shithub: psxe

Download patch

ref: 468ee688e68aaac0da90eef8082bb092d305b1e0
parent: ce32c2fd274db2a5d5302a3744758f62c6d8fd6c
author: allkern <aurealinbox@gmail.com>
date: Sun Apr 21 08:37:45 EDT 2024

Big update

--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -1,27 +1,27 @@
-name: macOS CI
-
-on:
-  push:
-    branches: [ "master" ]
-
-jobs:
-  build:
-    runs-on: macos-latest
-
-    steps:
-    - uses: actions/checkout@v3
-      with:
-        fetch-depth: 0
-    - name: Install SDL2 and dylibbundler
-      run: |
-           brew install sdl2
-           brew install dylibbundler
-    - name: Build and pack PSXE
-      run: |
-           git fetch --all --tags
-           ./build.sh
-           tar -czf psxe-macos-latest.tar.gz psxe.app
-    - uses: actions/upload-artifact@v3
-      with:
-        name: psxe-macos-latest
-        path: ./psxe-macos-latest.tar.gz
+name: macOS CI
+
+on:
+  push:
+    branches: [ "master" ]
+
+jobs:
+  build:
+    runs-on: macos-latest
+
+    steps:
+    - uses: actions/checkout@v3
+      with:
+        fetch-depth: 0
+    - name: Install SDL2 and dylibbundler
+      run: |
+           brew install sdl2
+           brew install dylibbundler
+    - name: Build and pack PSXE
+      run: |
+           git fetch --all --tags
+           ./build.sh
+           tar -czf psxe-macos-latest.tar.gz psxe.app
+    - uses: actions/upload-artifact@v3
+      with:
+        name: psxe-macos-latest
+        path: ./psxe-macos-latest.tar.gz
--- a/.github/workflows/ubuntu.yml
+++ b/.github/workflows/ubuntu.yml
@@ -1,31 +1,31 @@
-name: Ubuntu CI
-
-on:
-  push:
-    branches: [ "master" ]
-
-jobs:
-  build:
-    runs-on: ubuntu-latest
-
-    steps:
-    - uses: actions/checkout@v3
-      with:
-        fetch-depth: 0
-    - name: Install SDL2
-      run: |
-           sudo apt update
-           sudo apt install libsdl2-dev
-    - name: Build PSXE
-      run: |
-           git fetch --all --tags
-           make
-    - name: Pack executable
-      run: |
-           chmod +x ./bin/psxe
-           mv ./bin/psxe ./
-           tar -czf psxe-ubuntu-latest.tar.gz ./psxe
-    - uses: actions/upload-artifact@v3
-      with:
-        name: psxe-ubuntu-latest
-        path: ./psxe-ubuntu-latest.tar.gz
+name: Ubuntu CI
+
+on:
+  push:
+    branches: [ "master" ]
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v3
+      with:
+        fetch-depth: 0
+    - name: Install SDL2
+      run: |
+           sudo apt update
+           sudo apt install libsdl2-dev
+    - name: Build PSXE
+      run: |
+           git fetch --all --tags
+           make
+    - name: Pack executable
+      run: |
+           chmod +x ./bin/psxe
+           mv ./bin/psxe ./
+           tar -czf psxe-ubuntu-latest.tar.gz ./psxe
+    - uses: actions/upload-artifact@v3
+      with:
+        name: psxe-ubuntu-latest
+        path: ./psxe-ubuntu-latest.tar.gz
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -1,37 +1,37 @@
-name: Windows CI
-
-on:
-  push:
-    branches: [ "master" ]
-
-jobs:
-  build:
-    runs-on: windows-latest
-
-    steps:
-    - uses: actions/checkout@v3
-      with:
-        fetch-depth: 0
-    - name: Run build-deps
-      run: ./build-deps.ps1
-    - name: 64-bit build
-      run: |
-           ./build-win64.ps1
-           New-Item -Path "psxe" -ItemType Directory
-           Copy-Item -Recurse "bin" -Destination "psxe"
-    - uses: actions/upload-artifact@v3
-      with:
-        name: psxe-win64-latest
-        path: psxe/
-    - name: Cleanup
-      run: ./build-clean
-    - name: 32-bit build
-      run: |
-           Remove-Item -Path psxe -Recurse
-           ./build-win32.ps1
-           New-Item -Path "psxe" -ItemType Directory
-           Copy-Item -Recurse "bin" -Destination "psxe"
-    - uses: actions/upload-artifact@v3
-      with:
-        name: psxe-win32-latest
+name: Windows CI
+
+on:
+  push:
+    branches: [ "master" ]
+
+jobs:
+  build:
+    runs-on: windows-latest
+
+    steps:
+    - uses: actions/checkout@v3
+      with:
+        fetch-depth: 0
+    - name: Run build-deps
+      run: ./build-deps.ps1
+    - name: 64-bit build
+      run: |
+           ./build-win64.ps1
+           New-Item -Path "psxe" -ItemType Directory
+           Copy-Item -Recurse "bin" -Destination "psxe"
+    - uses: actions/upload-artifact@v3
+      with:
+        name: psxe-win64-latest
+        path: psxe/
+    - name: Cleanup
+      run: ./build-clean
+    - name: 32-bit build
+      run: |
+           Remove-Item -Path psxe -Recurse
+           ./build-win32.ps1
+           New-Item -Path "psxe" -ItemType Directory
+           Copy-Item -Recurse "bin" -Destination "psxe"
+    - uses: actions/upload-artifact@v3
+      with:
+        name: psxe-win32-latest
         path: psxe/
\ No newline at end of file
--- a/.gitignore
+++ b/.gitignore
@@ -1,24 +1,24 @@
-.vscode/
-bin/
-build-win32/
-imgui/
-res/
-sdl2/
-SDL2-2.26.5/
-sdl2-win32/
-sdl2-win64/
-test/
-bios/
-roms/
-snap/
-*.BIN
-*.bin
-*.dll
-*.exe
-*.out
-*.toml
-*.zip
-*.cue
-*.iso
+.vscode/
+bin/
+build-win32/
+imgui/
+res/
+sdl2/
+SDL2-2.26.5/
+sdl2-win32/
+sdl2-win64/
+test/
+bios/
+roms/
+snap/
+*.BIN
+*.bin
+*.dll
+*.exe
+*.out
+*.toml
+*.zip
+*.cue
+*.iso
 *.mcd
 *.rom
--- a/Makefile
+++ b/Makefile
@@ -1,33 +1,33 @@
-.ONESHELL:
-
-CFLAGS += -g -DLOG_USE_COLOR -lSDL2 -lSDL2main
-CFLAGS += -Ofast -Wno-overflow -Wall -pedantic -Wno-address-of-packed-member
-
-PLATFORM := $(shell uname -s)
-
-ifeq ($(PLATFORM),Darwin)
-	CFLAGS += -mmacosx-version-min=10.9 -Wno-newline-eof
-endif
-
-VERSION_TAG := $(shell git describe --always --tags --abbrev=0)
-COMMIT_HASH := $(shell git rev-parse --short HEAD)
-OS_INFO := $(shell uname -rmo)
-
-SOURCES := $(wildcard psx/*.c)
-SOURCES += $(wildcard psx/dev/*.c)
-SOURCES += $(wildcard psx/input/*.c)
-SOURCES += $(wildcard psx/disc/*.c)
-SOURCES += $(wildcard frontend/*.c)
-
-bin/psxe frontend/main.c:
-	mkdir -p bin
-
-	gcc $(SOURCES) -o bin/psxe \
-		-I"." \
-		-DOS_INFO="$(OS_INFO)" \
-		-DREP_VERSION="$(VERSION_TAG)" \
-		-DREP_COMMIT_HASH="$(COMMIT_HASH)" \
-		$(CFLAGS)
-
-clean:
-	rm -rf "bin"
+.ONESHELL:
+
+CFLAGS += -g -DLOG_USE_COLOR -lSDL2 -lSDL2main
+CFLAGS += -Ofast -Wno-overflow -Wall -pedantic -Wno-address-of-packed-member
+
+PLATFORM := $(shell uname -s)
+
+ifeq ($(PLATFORM),Darwin)
+	CFLAGS += -mmacosx-version-min=10.9 -Wno-newline-eof
+endif
+
+VERSION_TAG := $(shell git describe --always --tags --abbrev=0)
+COMMIT_HASH := $(shell git rev-parse --short HEAD)
+OS_INFO := $(shell uname -rmo)
+
+SOURCES := $(wildcard psx/*.c)
+SOURCES += $(wildcard psx/dev/*.c)
+SOURCES += $(wildcard psx/input/*.c)
+SOURCES += $(wildcard psx/disc/*.c)
+SOURCES += $(wildcard frontend/*.c)
+
+bin/psxe frontend/main.c:
+	mkdir -p bin
+
+	gcc $(SOURCES) -o bin/psxe \
+		-I"." \
+		-DOS_INFO="$(OS_INFO)" \
+		-DREP_VERSION="$(VERSION_TAG)" \
+		-DREP_COMMIT_HASH="$(COMMIT_HASH)" \
+		$(CFLAGS)
+
+clean:
+	rm -rf "bin"
--- a/README.md
+++ b/README.md
@@ -1,89 +1,89 @@
-# psxe
-A simple and portable Sony PlayStation emulator and emulation library written in C
-
-## Screenshots
-| Windows  | Ubuntu | macOS |
-| ------------- | ------------- | ------------- 
-| ![Mega Man X6 (USA)](https://github.com/allkern/psxe/assets/15825466/34dde8f9-eedb-4b44-a08d-c17026df2ff2) | ![Bloody Roar 2 - Bringer of the New Age (Europe)](https://github.com/allkern/psxe/assets/15825466/41a6dc67-b0ba-442f-bed6-7b207c0db4dd) | ![Parodius (Europe)](https://github.com/allkern/psxe/assets/15825466/9ab291d9-ec47-4997-92d3-23e38982ae45) |
-| ![Spyro 2 - Ripto's Rage (USA)](https://github.com/allkern/psxe/assets/15825466/e161ab66-af57-4327-9a94-8b2591a0012a) | ![Namco Museum Vol. 1 (USA)](https://github.com/allkern/psxe/assets/15825466/67ea61e4-5f30-470c-a978-23e0755850b6) | ![Darius Gaiden (Japan)](https://github.com/allkern/psxe/assets/15825466/0c55118c-ab42-40e5-b34a-7594528080bf) |
-
-### CI status
-![Windows](https://github.com/allkern/psx/actions/workflows/windows.yml/badge.svg)
-![macOS](https://github.com/allkern/psx/actions/workflows/macos.yml/badge.svg)
-![Ubuntu](https://github.com/allkern/psx/actions/workflows/ubuntu.yml/badge.svg)
-
-## Running
-You can download the latest automated build for your platform on Releases. If your system isn't supported, you can easily build the emulator from source, instructions on "Building" below.
-
-In order to run the emulator, you will need a BIOS file. You can either get one from the internet or [dump it from your own console](https://www.youtube.com/watch?v=u8eHp0COcBo).
-
-Most BIOS versions are confirmed to work.
-
-Use the `-b` or `--bios` setting to configure the BIOS file.
-
-## Progress
-All components have been implemented, those with an orange circle are WIP, while the green ones are largely working correctly.
-
-<img src="https://github.com/allkern/psxe/assets/15825466/199c20e4-4e7e-4d0a-a033-eda347034ed5" width="12" height="12"/> CPU </br>
-<img src="https://github.com/allkern/psxe/assets/15825466/199c20e4-4e7e-4d0a-a033-eda347034ed5" width="12" height="12"/> DMA </br>
-<img src="https://github.com/allkern/psxe/assets/15825466/0ed1fe97-de2f-47de-bb30-82286e6c5fa0" width="12" height="12"/> GPU </br>
-<img src="https://github.com/allkern/psxe/assets/15825466/0ed1fe97-de2f-47de-bb30-82286e6c5fa0" width="12" height="12"/> CDROM </br>
-<img src="https://github.com/allkern/psxe/assets/15825466/0ed1fe97-de2f-47de-bb30-82286e6c5fa0" width="12" height="12"/> SPU </br>
-<img src="https://github.com/allkern/psxe/assets/15825466/0ed1fe97-de2f-47de-bb30-82286e6c5fa0" width="12" height="12"/> MDEC </br>
-<img src="https://github.com/allkern/psxe/assets/15825466/0ed1fe97-de2f-47de-bb30-82286e6c5fa0" width="12" height="12"/> Timers </br>
-<img src="https://github.com/allkern/psxe/assets/15825466/0ed1fe97-de2f-47de-bb30-82286e6c5fa0" width="12" height="12"/> GTE </br>
-
-## Building
-Building the emulator should be easy, just use the scripts provided in this repo.
-
-On Windows, the `build-deps.ps1` script downloads SDL2 and unzips it. If you want to run the emulator standalone, you will have to move the SDL2 DLL to the same folder where the executable is located.
-
-**If you already have SDL2 on your system**, you can skip running `build-deps.ps1`. Though you will have to edit `build-win.ps1` to point the `SDL2_DIR` variable to your installation path.
-
-On Ubuntu, you will also need to install `libsdl2-dev`, you can get it from `apt` like so:
-```
-sudo apt update
-sudo apt upgrade
-sudo apt install libsdl2-dev
-```
-
-Building on macOS requires installing SDL2 and dylibbundler, this can be done using `brew`:
-```
-brew install sdl2
-brew install dylibbundler
-```
-
-Assuming you did everything described above, you should be able to build the emulator by using the following commands.
-
-### Windows
-```
-git clone https://github.com/allkern/psxe
-cd psx
-./build-deps
-./build-win.ps1
-```
-On rare cases these scripts might not work (PowerShell/Windows bugs). If so, please open an issue on the issues tab with information about your system so we can make sure we cover the maximum amount of systems. 
-
-### Ubuntu
-```
-git clone https://github.com/allkern/psxe
-cd psx
-make clean && make
-```
-
-### macOS
-```
-git clone https://github.com/allkern/psxe
-cd psx
-./build.sh
-```
-
-## Acknowledgements
-This project uses external open source code that can be found on the following GitHub repos:
-- argparse.c: https://github.com/cofyc/argparse
-- log.c (slightly modified): https://github.com/rxi/log.c
-- tomlc99: https://github.com/cktan/tomlc99
-
-Their original licenses are respected and apply to the code in this project.
-
-As always, thanks to all original developers for their amazing work.
+# psxe
+A simple and portable Sony PlayStation emulator and emulation library written in C
+
+## Screenshots
+| Windows  | Ubuntu | macOS |
+| ------------- | ------------- | ------------- 
+| ![Mega Man X6 (USA)](https://github.com/allkern/psxe/assets/15825466/34dde8f9-eedb-4b44-a08d-c17026df2ff2) | ![Bloody Roar 2 - Bringer of the New Age (Europe)](https://github.com/allkern/psxe/assets/15825466/41a6dc67-b0ba-442f-bed6-7b207c0db4dd) | ![Parodius (Europe)](https://github.com/allkern/psxe/assets/15825466/9ab291d9-ec47-4997-92d3-23e38982ae45) |
+| ![Spyro 2 - Ripto's Rage (USA)](https://github.com/allkern/psxe/assets/15825466/e161ab66-af57-4327-9a94-8b2591a0012a) | ![Namco Museum Vol. 1 (USA)](https://github.com/allkern/psxe/assets/15825466/67ea61e4-5f30-470c-a978-23e0755850b6) | ![Darius Gaiden (Japan)](https://github.com/allkern/psxe/assets/15825466/0c55118c-ab42-40e5-b34a-7594528080bf) |
+
+### CI status
+![Windows](https://github.com/allkern/psx/actions/workflows/windows.yml/badge.svg)
+![macOS](https://github.com/allkern/psx/actions/workflows/macos.yml/badge.svg)
+![Ubuntu](https://github.com/allkern/psx/actions/workflows/ubuntu.yml/badge.svg)
+
+## Running
+You can download the latest automated build for your platform on Releases. If your system isn't supported, you can easily build the emulator from source, instructions on "Building" below.
+
+In order to run the emulator, you will need a BIOS file. You can either get one from the internet or [dump it from your own console](https://www.youtube.com/watch?v=u8eHp0COcBo).
+
+Most BIOS versions are confirmed to work.
+
+Use the `-b` or `--bios` setting to configure the BIOS file.
+
+## Progress
+All components have been implemented, those with an orange circle are WIP, while the green ones are largely working correctly.
+
+<img src="https://github.com/allkern/psxe/assets/15825466/199c20e4-4e7e-4d0a-a033-eda347034ed5" width="12" height="12"/> CPU </br>
+<img src="https://github.com/allkern/psxe/assets/15825466/199c20e4-4e7e-4d0a-a033-eda347034ed5" width="12" height="12"/> DMA </br>
+<img src="https://github.com/allkern/psxe/assets/15825466/0ed1fe97-de2f-47de-bb30-82286e6c5fa0" width="12" height="12"/> GPU </br>
+<img src="https://github.com/allkern/psxe/assets/15825466/0ed1fe97-de2f-47de-bb30-82286e6c5fa0" width="12" height="12"/> CDROM </br>
+<img src="https://github.com/allkern/psxe/assets/15825466/0ed1fe97-de2f-47de-bb30-82286e6c5fa0" width="12" height="12"/> SPU </br>
+<img src="https://github.com/allkern/psxe/assets/15825466/0ed1fe97-de2f-47de-bb30-82286e6c5fa0" width="12" height="12"/> MDEC </br>
+<img src="https://github.com/allkern/psxe/assets/15825466/0ed1fe97-de2f-47de-bb30-82286e6c5fa0" width="12" height="12"/> Timers </br>
+<img src="https://github.com/allkern/psxe/assets/15825466/0ed1fe97-de2f-47de-bb30-82286e6c5fa0" width="12" height="12"/> GTE </br>
+
+## Building
+Building the emulator should be easy, just use the scripts provided in this repo.
+
+On Windows, the `build-deps.ps1` script downloads SDL2 and unzips it. If you want to run the emulator standalone, you will have to move the SDL2 DLL to the same folder where the executable is located.
+
+**If you already have SDL2 on your system**, you can skip running `build-deps.ps1`. Though you will have to edit `build-win.ps1` to point the `SDL2_DIR` variable to your installation path.
+
+On Ubuntu, you will also need to install `libsdl2-dev`, you can get it from `apt` like so:
+```
+sudo apt update
+sudo apt upgrade
+sudo apt install libsdl2-dev
+```
+
+Building on macOS requires installing SDL2 and dylibbundler, this can be done using `brew`:
+```
+brew install sdl2
+brew install dylibbundler
+```
+
+Assuming you did everything described above, you should be able to build the emulator by using the following commands.
+
+### Windows
+```
+git clone https://github.com/allkern/psxe
+cd psx
+./build-deps
+./build-win.ps1
+```
+On rare cases these scripts might not work (PowerShell/Windows bugs). If so, please open an issue on the issues tab with information about your system so we can make sure we cover the maximum amount of systems. 
+
+### Ubuntu
+```
+git clone https://github.com/allkern/psxe
+cd psx
+make clean && make
+```
+
+### macOS
+```
+git clone https://github.com/allkern/psxe
+cd psx
+./build.sh
+```
+
+## Acknowledgements
+This project uses external open source code that can be found on the following GitHub repos:
+- argparse.c: https://github.com/cofyc/argparse
+- log.c (slightly modified): https://github.com/rxi/log.c
+- tomlc99: https://github.com/cktan/tomlc99
+
+Their original licenses are respected and apply to the code in this project.
+
+As always, thanks to all original developers for their amazing work.
--- a/build-deps.ps1
+++ b/build-deps.ps1
@@ -1,20 +1,20 @@
-if (Test-Path "SDL2-2.26.5") {
-    Remove-Item -Recurse "SDL2-2.26.5"
-}
-
-$SDL2_URL = "https://github.com/libsdl-org/SDL/releases/download/release-2.26.5/SDL2-devel-2.26.5-mingw.zip"
-$WIN32_URL = "https://github.com/libsdl-org/SDL/releases/download/release-2.26.5/SDL2-2.26.5-win32-x86.zip"
-$WIN64_URL = "https://github.com/libsdl-org/SDL/releases/download/release-2.26.5/SDL2-2.26.5-win32-x64.zip"
-
-Invoke-WebRequest -URI $SDL2_URL -OutFile "sdl2.zip"
-Expand-Archive "sdl2.zip" -DestinationPath "." -Force
-
-Invoke-WebRequest -URI $WIN32_URL -OutFile "sdl2-win32.zip"
-Expand-Archive "sdl2-win32.zip" -DestinationPath "sdl2-win32" -Force
-
-Invoke-WebRequest -URI $WIN64_URL -OutFile "sdl2-win64.zip"
-Expand-Archive "sdl2-win64.zip" -DestinationPath "sdl2-win64" -Force
-
-Remove-Item "sdl2.zip"
-Remove-Item "sdl2-win32.zip"
+if (Test-Path "SDL2-2.26.5") {
+    Remove-Item -Recurse "SDL2-2.26.5"
+}
+
+$SDL2_URL = "https://github.com/libsdl-org/SDL/releases/download/release-2.26.5/SDL2-devel-2.26.5-mingw.zip"
+$WIN32_URL = "https://github.com/libsdl-org/SDL/releases/download/release-2.26.5/SDL2-2.26.5-win32-x86.zip"
+$WIN64_URL = "https://github.com/libsdl-org/SDL/releases/download/release-2.26.5/SDL2-2.26.5-win32-x64.zip"
+
+Invoke-WebRequest -URI $SDL2_URL -OutFile "sdl2.zip"
+Expand-Archive "sdl2.zip" -DestinationPath "." -Force
+
+Invoke-WebRequest -URI $WIN32_URL -OutFile "sdl2-win32.zip"
+Expand-Archive "sdl2-win32.zip" -DestinationPath "sdl2-win32" -Force
+
+Invoke-WebRequest -URI $WIN64_URL -OutFile "sdl2-win64.zip"
+Expand-Archive "sdl2-win64.zip" -DestinationPath "sdl2-win64" -Force
+
+Remove-Item "sdl2.zip"
+Remove-Item "sdl2-win32.zip"
 Remove-Item "sdl2-win64.zip"
\ No newline at end of file
--- a/build-win32.ps1
+++ b/build-win32.ps1
@@ -1,29 +1,29 @@
-git fetch --all --tags
-
-$VERSION_TAG = git describe --always --tags --abbrev=0
-$COMMIT_HASH = git rev-parse --short HEAD
-$OS_INFO = (Get-WMIObject win32_operatingsystem).caption + " " + (Get-WMIObject win32_operatingsystem).version + " " + (Get-WMIObject win32_operatingsystem).OSArchitecture
-
-$SDL2_DIR = "SDL2-2.26.5\x86_64-w64-mingw32"
-$PSX_DIR = "."
-
-mkdir -Force -Path bin > $null
-
-gcc -I"`"$($PSX_DIR)`"" `
-    -I"`"$($SDL2_DIR)\include`"" `
-    -I"`"$($SDL2_DIR)\include\SDL2`"" `
-    "psx\*.c" `
-    "psx\dev\*.c" `
-    "psx\input\*.c" `
-    "psx\disc\*.c" `
-    "frontend\*.c" `
-    -o "bin\psxe.exe" `
-    -DREP_VERSION="`"$($VERSION_TAG)`"" `
-    -DREP_COMMIT_HASH="`"$($COMMIT_HASH)`"" `
-    -DOS_INFO="`"$($OS_INFO)`"" `
-    -L"`"$($SDL2_DIR)\lib`"" `
-    -lSDL2main -lSDL2 -Wno-overflow `
-    -Wall -pedantic -DLOG_USE_COLOR `
-    -ffast-math -Ofast
-
+git fetch --all --tags
+
+$VERSION_TAG = git describe --always --tags --abbrev=0
+$COMMIT_HASH = git rev-parse --short HEAD
+$OS_INFO = (Get-WMIObject win32_operatingsystem).caption + " " + (Get-WMIObject win32_operatingsystem).version + " " + (Get-WMIObject win32_operatingsystem).OSArchitecture
+
+$SDL2_DIR = "SDL2-2.26.5\x86_64-w64-mingw32"
+$PSX_DIR = "."
+
+mkdir -Force -Path bin > $null
+
+gcc -I"`"$($PSX_DIR)`"" `
+    -I"`"$($SDL2_DIR)\include`"" `
+    -I"`"$($SDL2_DIR)\include\SDL2`"" `
+    "psx\*.c" `
+    "psx\dev\*.c" `
+    "psx\input\*.c" `
+    "psx\disc\*.c" `
+    "frontend\*.c" `
+    -o "bin\psxe.exe" `
+    -DREP_VERSION="`"$($VERSION_TAG)`"" `
+    -DREP_COMMIT_HASH="`"$($COMMIT_HASH)`"" `
+    -DOS_INFO="`"$($OS_INFO)`"" `
+    -L"`"$($SDL2_DIR)\lib`"" `
+    -lSDL2main -lSDL2 -Wno-overflow `
+    -Wall -pedantic -DLOG_USE_COLOR `
+    -ffast-math -Ofast
+
 Copy-Item -Path "sdl2-win32/SDL2.dll" -Destination "bin"
\ No newline at end of file
--- a/build-win64.ps1
+++ b/build-win64.ps1
@@ -1,30 +1,30 @@
-git fetch --all --tags
-
-$VERSION_TAG = git describe --always --tags --abbrev=0
-$COMMIT_HASH = git rev-parse --short HEAD
-$OS_INFO = (Get-WMIObject win32_operatingsystem).caption + " " + (Get-WMIObject win32_operatingsystem).version + " " + (Get-WMIObject win32_operatingsystem).OSArchitecture
-
-$SDL2_DIR = "SDL2-2.26.5\x86_64-w64-mingw32"
-$PSX_DIR = "."
-
-mkdir -Force -Path bin > $null
-
-gcc -I"`"$($PSX_DIR)`"" `
-    -I"`"$($SDL2_DIR)\include`"" `
-    -I"`"$($SDL2_DIR)\include\SDL2`"" `
-    "psx\*.c" `
-    "psx\dev\*.c" `
-    "psx\input\*.c" `
-    "psx\disc\*.c" `
-    "frontend\*.c" `
-    -o "bin\psxe.exe" `
-    -DREP_VERSION="`"$($VERSION_TAG)`"" `
-    -DREP_COMMIT_HASH="`"$($COMMIT_HASH)`"" `
-    -DOS_INFO="`"$($OS_INFO)`"" `
-    -L"`"$($SDL2_DIR)\lib`"" `
-    -m64 -lSDL2main -lSDL2 -Wno-overflow `
-    -Wall -pedantic -DLOG_USE_COLOR `
-    -Wno-address-of-packed-member `
-    -ffast-math -Ofast -g
-
+git fetch --all --tags
+
+$VERSION_TAG = git describe --always --tags --abbrev=0
+$COMMIT_HASH = git rev-parse --short HEAD
+$OS_INFO = (Get-WMIObject win32_operatingsystem).caption + " " + (Get-WMIObject win32_operatingsystem).version + " " + (Get-WMIObject win32_operatingsystem).OSArchitecture
+
+$SDL2_DIR = "SDL2-2.26.5\x86_64-w64-mingw32"
+$PSX_DIR = "."
+
+mkdir -Force -Path bin > $null
+
+gcc -I"`"$($PSX_DIR)`"" `
+    -I"`"$($SDL2_DIR)\include`"" `
+    -I"`"$($SDL2_DIR)\include\SDL2`"" `
+    "psx\*.c" `
+    "psx\dev\*.c" `
+    "psx\input\*.c" `
+    "psx\disc\*.c" `
+    "frontend\*.c" `
+    -o "bin\psxe.exe" `
+    -DREP_VERSION="`"$($VERSION_TAG)`"" `
+    -DREP_COMMIT_HASH="`"$($COMMIT_HASH)`"" `
+    -DOS_INFO="`"$($OS_INFO)`"" `
+    -L"`"$($SDL2_DIR)\lib`"" `
+    -m64 -lSDL2main -lSDL2 -Wno-overflow `
+    -Wall -pedantic -DLOG_USE_COLOR `
+    -Wno-address-of-packed-member `
+    -ffast-math -Ofast -g
+
 Copy-Item -Path "sdl2-win64/SDL2.dll" -Destination "bin"
\ No newline at end of file
--- a/frontend/common.h
+++ b/frontend/common.h
@@ -1,17 +1,17 @@
-#ifndef COMMON_H
-#define COMMON_H
-
-#ifndef OS_INFO
-#define OS_INFO unknown
-#endif
-#ifndef REP_VERSION
-#define REP_VERSION latest
-#endif
-#ifndef REP_COMMIT_HASH
-#define REP_COMMIT_HASH latest
-#endif
-
-#define STR1(m) #m
-#define STR(m) STR1(m)
-
+#ifndef COMMON_H
+#define COMMON_H
+
+#ifndef OS_INFO
+#define OS_INFO unknown
+#endif
+#ifndef REP_VERSION
+#define REP_VERSION latest
+#endif
+#ifndef REP_COMMIT_HASH
+#define REP_COMMIT_HASH latest
+#endif
+
+#define STR1(m) #m
+#define STR(m) STR1(m)
+
 #endif
\ No newline at end of file
--- a/frontend/config.c
+++ b/frontend/config.c
@@ -1,291 +1,291 @@
-#include <string.h>
-#include <stdlib.h>
-
-#include "config.h"
-#include "common.h"
-
-static const char* g_version_text =
-#ifdef _WIN32
-    "psxe.exe (" STR(OS_INFO) ") " STR(REP_VERSION) "-" STR(REP_COMMIT_HASH) "\n"
-#elif __linux__
-    "psxe (" STR(OS_INFO) ") " STR(REP_VERSION) "-" STR(REP_COMMIT_HASH) "\n"
-#else
-    "psxe (" STR(OS_INFO) ") " STR(REP_VERSION) "-" STR(REP_COMMIT_HASH) "\n"
-#endif
-    "\nPSXE - A simple, fast and portable Sony PlayStation emulator.\n\n"
-    "Copyright (C) 2023 Allkern (Lisandro Alarcon)\n"
-    "This is free software; see the source for copying conditions.  There is NO\n"
-    "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n";
-
-static const char g_default_settings[] =
-    "# Settings file generated by PSXE\n"
-    "\n"
-    "# Don't change please, reserved for future use\n"
-    "psxe_version = \"" STR(REP_VERSION) "\"\n"
-    "\n"
-    "# BIOS-related settings:\n"
-    "[bios]\n"
-    "    search_path     = \"bios\"\n"
-    "    preferred_model = \"SCPH-1001\"\n"
-    "    override_file   = \"\"\n"
-    "\n"
-    "# Console settings\n"
-    "[console]\n"
-    "    region          = \"auto\"\n";
-
-static const char* g_models_text =
-    "Available console models:\n"
-    "\"scph1000\" (SCPH-1000) [NTSC-J]\n"
-    "\"scph1001\" (SCPH-1001) [NTSC-U/C]\n"
-    "\"scph1002\" (SCPH-1002) [PAL]\n"
-    "\"scph3000\" (SCPH-3000) [NTSC-J]\n"
-    "\"scph3500\" (SCPH-3500) [NTSC-J]\n"
-    "\"scph5000\" (SCPH-5000) [NTSC-U/C]\n"
-    "\"scph5501\" (SCPH-5501) [NTSC-U/C]\n"
-    "\"scph5500\" (SCPH-5500) [NTSC-J]\n"
-    "\"scph5502\" (SCPH-5502) [PAL]\n"
-    "\"scph5552\" (SCPH-5552) [PAL]\n"
-    "\"scph7000\" (SCPH-7000) [NTSC-J]\n"
-    "\"scph7001\" (SCPH-7001) [NTSC-U/C]\n"
-    "\"scph7002\" (SCPH-7002) [PAL]\n"
-    "\"scph7003\" (SCPH-7003) [NTSC-J]\n"
-    "\"scph7501\" (SCPH-7501) [NTSC]\n"
-    "\"scph7502\" (SCPH-7502) [PAL]\n"
-    "\"scph9002\" (SCPH-9002) [PAL]\n"
-    "\"scph100\" (SCPH-100) [NTSC-J]\n"
-    "\"scph101\" (SCPH-101) [NTSC-U/C]\n"
-    "\"scph102a\" (SCPH-102A) [PAL]\n"
-    "\"scph102b\" (SCPH-102B) [PAL]\n"
-    "\"scph102c\" (SCPH-102C) [?]\n";
-
-static const char* g_regions_text =
-    "Available region options: \"ntsc\", \"pal\", \"auto\"\n";
-
-static const char* g_desc_text =
-    "\nPlease report any bugs to <https://github.com/allkern/psxe/issues>\n";
-
-psxe_config_t* psxe_cfg_create() {
-    return (psxe_config_t*)malloc(sizeof(psxe_config_t));
-}
-
-void psxe_cfg_init(psxe_config_t* cfg) {
-    memset(cfg, 0, sizeof(psxe_config_t));
-}
-
-void psxe_cfg_destroy(psxe_config_t* cfg) {
-    free(cfg);
-}
-
-void psxe_cfg_load_defaults(psxe_config_t* cfg) {
-    cfg->bios = "bios.bin";
-    cfg->bios_search = "bios";
-    cfg->exe = NULL;
-    cfg->help_model = 0;
-    cfg->help_region = 0;
-    cfg->model = "scph1001";
-    cfg->psxe_version = STR(REP_VERSION);
-    cfg->region = "ntsc";
-    cfg->settings_path = NULL;
-    cfg->use_args = 0;
-    cfg->version = 0;
-    cfg->log_level = LOG_FATAL;
-    cfg->quiet = 0;
-    cfg->cd_path = NULL;
-    cfg->exp_path = NULL;
-}
-
-void psxe_cfg_load(psxe_config_t* cfg, int argc, const char* argv[]) {
-    log_set_level(LOG_INFO);
-
-    int use_args = 0;
-    int version = 0;
-    int help_model = 0;
-    int help_region = 0;
-    int log_level = 0;
-    int quiet = 0;
-    int console_source = 0;
-    const char* settings_path = NULL;
-    const char* bios = NULL;
-    const char* bios_search = NULL;
-    const char* model = NULL;
-    const char* exe = NULL;
-    const char* region = NULL;
-    const char* psxe_version = NULL;
-    const char* cd_path = NULL;
-    const char* exp_path = NULL;
-
-    static const char *const usages[] = {
-        "psxe [options] path-to-cdrom",
-        NULL,
-    };
-
-    struct argparse_option options[] = {
-        OPT_BOOLEAN ('h', "help"          , NULL           , "Display this information", argparse_help_cb, 0, 0),
-        OPT_BOOLEAN (0  , "help-model"    , &help_model    , "Display available console models", NULL, 0, 0),
-        OPT_BOOLEAN (0  , "help-region"   , &help_region   , "Display available region options", NULL, 0, 0),
-        OPT_BOOLEAN ('v', "version"       , &version       , "Display version and build information", NULL, 0, 0),
-        OPT_GROUP("Basic options"),
-        OPT_BOOLEAN ('a', "use-args"      , &use_args      , "Ignore settings file, use CLI args instead", NULL, 0, 0),
-        OPT_STRING  ('b', "bios"          , &bios          , "Specify a BIOS file (ignores -B, -M)", NULL, 0, 0),
-        OPT_BOOLEAN ('B', "bios-folder"   , &bios_search   , "Specify a BIOS search folder", NULL, 0, 0),
-        OPT_STRING  ('c', "console-source", &console_source, "Select console source (auto, null, kernel, atcons)"),
-        OPT_STRING  ('e', "exp-rom"       , &exp_path      , "Specify an expansion ROM file"),
-        OPT_INTEGER ('L', "log-level"     , &log_level     , "Set log level"),
-        OPT_STRING  ('M', "model"         , &model         , "Specify console model (SPCH-XXXX)", NULL, 0, 0),
-        OPT_STRING  ('r', "region"        , &region        , "Specify console region"),
-        OPT_STRING  ('S', "settings-file" , &settings_path , "Specify settings file path", NULL, 0, 0),
-        OPT_BOOLEAN ('q', "quiet"         , &quiet         , "Silence all logs (ignores -L)"),
-        OPT_STRING  ('x', "exe"           , &exe           , "Launch a PS-X EXE file"),
-        OPT_STRING  (0  , "cdrom"         , &cd_path       , "Specify a CDROM image"),
-        OPT_END()
-    };
-
-    struct argparse argparse;
-
-    argparse_init(&argparse, options, usages, 0);
-    argparse_describe(&argparse, NULL, g_desc_text);
-
-    argc = argparse_parse(&argparse, argc, argv);
-
-    if (help_model) {
-        printf("%s\n", g_models_text);
-
-        exit(0);
-    }
-
-    if (help_region) {
-        printf("%s\n", g_regions_text);
-
-        exit(0);
-    }
-
-    if (version) {
-        printf("%s\n", g_version_text);
-
-        exit(0);
-    }
-
-    log_set_quiet(quiet);
-
-    if (!use_args) {
-        if (!settings_path)
-            settings_path = "settings.toml";
-
-        FILE* settings = fopen(settings_path, "rb");
-
-        char error[0x100];
-
-        if (!settings) {
-            settings = fopen("settings.toml", "w+b");
-
-            if (!settings) {
-                log_error("Couldn't create settings file, loading default settings");
-
-                psxe_cfg_load_defaults(cfg);
-
-                return;
-            }
-
-            fwrite(g_default_settings, 1, sizeof(g_default_settings) - 1, settings);
-
-            fseek(settings, 0, 0);
-        }
-
-        log_info("Parsing settings file...");
-
-        toml_table_t* conf = toml_parse_file(settings, error, sizeof(error));
-
-        if (!conf) {
-            log_error("Couldn't parse settings file");
-
-            exit(1);
-        }
-
-        toml_datum_t s_version = toml_string_in(conf, "psxe_version");
-
-        if (!s_version.ok) {
-            log_error("Settings file lacking version number");
-
-            exit(1);
-        }
-
-        toml_table_t* s_bios_table = toml_table_in(conf, "bios");
-
-        if (s_bios_table) {
-            toml_datum_t s_bios_search_path = toml_string_in(s_bios_table, "search_path");
-
-            if (s_bios_search_path.ok)
-                bios_search = s_bios_search_path.u.s;
-
-            toml_datum_t s_bios_preferred_model = toml_string_in(s_bios_table, "preferred_model");
-
-            if (s_bios_preferred_model.ok)
-                model = s_bios_preferred_model.u.s;
-
-            toml_datum_t s_bios_override_file = toml_string_in(s_bios_table, "override_file");
-
-            if (s_bios_override_file.ok)
-                bios = s_bios_override_file.u.s;
-        }
-
-        toml_table_t* s_console_table = toml_table_in(conf, "console");
-
-        if (s_console_table) {
-            toml_datum_t s_console_region = toml_string_in(s_console_table, "region");
-
-            if (s_console_region.ok)
-                region = s_console_region.u.s;
-        }
-
-        psxe_version = s_version.u.s;
-
-        log_info("Settings file parsed. PSXE version: %s", psxe_version);
-
-        fclose(settings);
-    }
-
-    if (argc) {
-        if (argc > 1) {
-            log_error("Unrecognized parameter \'%s\'", argv[1]);
-
-            exit(1);
-        }
-
-        cd_path = argv[0];
-    }
-
-    if (cd_path)
-        cfg->cd_path = cd_path;
-
-    if (log_level)
-        cfg->log_level = log_level - 1;
-
-    if (bios)
-        cfg->bios = bios;
-
-    if (bios_search)
-        cfg->bios_search = bios_search;
-
-    if (model)
-        cfg->model = model;
-
-    if (exe)
-        cfg->exe = exe;
-
-    if (region)
-        cfg->region = region;
-
-    if (psxe_version)
-        cfg->psxe_version = psxe_version;
-
-    if (exp_path)
-        cfg->exp_path = exp_path;
-}
-
-// To-do: Implement BIOS searching
-char* psxe_cfg_get_bios_path(psxe_config_t* cfg) {
-    return NULL;
-}
-
-#undef STR1
+#include <string.h>
+#include <stdlib.h>
+
+#include "config.h"
+#include "common.h"
+
+static const char* g_version_text =
+#ifdef _WIN32
+    "psxe.exe (" STR(OS_INFO) ") " STR(REP_VERSION) "-" STR(REP_COMMIT_HASH) "\n"
+#elif __linux__
+    "psxe (" STR(OS_INFO) ") " STR(REP_VERSION) "-" STR(REP_COMMIT_HASH) "\n"
+#else
+    "psxe (" STR(OS_INFO) ") " STR(REP_VERSION) "-" STR(REP_COMMIT_HASH) "\n"
+#endif
+    "\nPSXE - A simple, fast and portable Sony PlayStation emulator.\n\n"
+    "Copyright (C) 2023 Allkern (Lisandro Alarcon)\n"
+    "This is free software; see the source for copying conditions.  There is NO\n"
+    "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n";
+
+static const char g_default_settings[] =
+    "# Settings file generated by PSXE\n"
+    "\n"
+    "# Don't change please, reserved for future use\n"
+    "psxe_version = \"" STR(REP_VERSION) "\"\n"
+    "\n"
+    "# BIOS-related settings:\n"
+    "[bios]\n"
+    "    search_path     = \"bios\"\n"
+    "    preferred_model = \"SCPH-1001\"\n"
+    "    override_file   = \"\"\n"
+    "\n"
+    "# Console settings\n"
+    "[console]\n"
+    "    region          = \"auto\"\n";
+
+static const char* g_models_text =
+    "Available console models:\n"
+    "\"scph1000\" (SCPH-1000) [NTSC-J]\n"
+    "\"scph1001\" (SCPH-1001) [NTSC-U/C]\n"
+    "\"scph1002\" (SCPH-1002) [PAL]\n"
+    "\"scph3000\" (SCPH-3000) [NTSC-J]\n"
+    "\"scph3500\" (SCPH-3500) [NTSC-J]\n"
+    "\"scph5000\" (SCPH-5000) [NTSC-U/C]\n"
+    "\"scph5501\" (SCPH-5501) [NTSC-U/C]\n"
+    "\"scph5500\" (SCPH-5500) [NTSC-J]\n"
+    "\"scph5502\" (SCPH-5502) [PAL]\n"
+    "\"scph5552\" (SCPH-5552) [PAL]\n"
+    "\"scph7000\" (SCPH-7000) [NTSC-J]\n"
+    "\"scph7001\" (SCPH-7001) [NTSC-U/C]\n"
+    "\"scph7002\" (SCPH-7002) [PAL]\n"
+    "\"scph7003\" (SCPH-7003) [NTSC-J]\n"
+    "\"scph7501\" (SCPH-7501) [NTSC]\n"
+    "\"scph7502\" (SCPH-7502) [PAL]\n"
+    "\"scph9002\" (SCPH-9002) [PAL]\n"
+    "\"scph100\" (SCPH-100) [NTSC-J]\n"
+    "\"scph101\" (SCPH-101) [NTSC-U/C]\n"
+    "\"scph102a\" (SCPH-102A) [PAL]\n"
+    "\"scph102b\" (SCPH-102B) [PAL]\n"
+    "\"scph102c\" (SCPH-102C) [?]\n";
+
+static const char* g_regions_text =
+    "Available region options: \"ntsc\", \"pal\", \"auto\"\n";
+
+static const char* g_desc_text =
+    "\nPlease report any bugs to <https://github.com/allkern/psxe/issues>\n";
+
+psxe_config_t* psxe_cfg_create() {
+    return (psxe_config_t*)malloc(sizeof(psxe_config_t));
+}
+
+void psxe_cfg_init(psxe_config_t* cfg) {
+    memset(cfg, 0, sizeof(psxe_config_t));
+}
+
+void psxe_cfg_destroy(psxe_config_t* cfg) {
+    free(cfg);
+}
+
+void psxe_cfg_load_defaults(psxe_config_t* cfg) {
+    cfg->bios = "bios.bin";
+    cfg->bios_search = "bios";
+    cfg->exe = NULL;
+    cfg->help_model = 0;
+    cfg->help_region = 0;
+    cfg->model = "scph1001";
+    cfg->psxe_version = STR(REP_VERSION);
+    cfg->region = "ntsc";
+    cfg->settings_path = NULL;
+    cfg->use_args = 0;
+    cfg->version = 0;
+    cfg->log_level = LOG_FATAL;
+    cfg->quiet = 0;
+    cfg->cd_path = NULL;
+    cfg->exp_path = NULL;
+}
+
+void psxe_cfg_load(psxe_config_t* cfg, int argc, const char* argv[]) {
+    log_set_level(LOG_INFO);
+
+    int use_args = 0;
+    int version = 0;
+    int help_model = 0;
+    int help_region = 0;
+    int log_level = 0;
+    int quiet = 0;
+    int console_source = 0;
+    const char* settings_path = NULL;
+    const char* bios = NULL;
+    const char* bios_search = NULL;
+    const char* model = NULL;
+    const char* exe = NULL;
+    const char* region = NULL;
+    const char* psxe_version = NULL;
+    const char* cd_path = NULL;
+    const char* exp_path = NULL;
+
+    static const char *const usages[] = {
+        "psxe [options] path-to-cdrom",
+        NULL,
+    };
+
+    struct argparse_option options[] = {
+        OPT_BOOLEAN ('h', "help"          , NULL           , "Display this information", argparse_help_cb, 0, 0),
+        OPT_BOOLEAN (0  , "help-model"    , &help_model    , "Display available console models", NULL, 0, 0),
+        OPT_BOOLEAN (0  , "help-region"   , &help_region   , "Display available region options", NULL, 0, 0),
+        OPT_BOOLEAN ('v', "version"       , &version       , "Display version and build information", NULL, 0, 0),
+        OPT_GROUP("Basic options"),
+        OPT_BOOLEAN ('a', "use-args"      , &use_args      , "Ignore settings file, use CLI args instead", NULL, 0, 0),
+        OPT_STRING  ('b', "bios"          , &bios          , "Specify a BIOS file (ignores -B, -M)", NULL, 0, 0),
+        OPT_BOOLEAN ('B', "bios-folder"   , &bios_search   , "Specify a BIOS search folder", NULL, 0, 0),
+        OPT_STRING  ('c', "console-source", &console_source, "Select console source (auto, null, kernel, atcons)"),
+        OPT_STRING  ('e', "exp-rom"       , &exp_path      , "Specify an expansion ROM file"),
+        OPT_INTEGER ('L', "log-level"     , &log_level     , "Set log level"),
+        OPT_STRING  ('M', "model"         , &model         , "Specify console model (SPCH-XXXX)", NULL, 0, 0),
+        OPT_STRING  ('r', "region"        , &region        , "Specify console region"),
+        OPT_STRING  ('S', "settings-file" , &settings_path , "Specify settings file path", NULL, 0, 0),
+        OPT_BOOLEAN ('q', "quiet"         , &quiet         , "Silence all logs (ignores -L)"),
+        OPT_STRING  ('x', "exe"           , &exe           , "Launch a PS-X EXE file"),
+        OPT_STRING  (0  , "cdrom"         , &cd_path       , "Specify a CDROM image"),
+        OPT_END()
+    };
+
+    struct argparse argparse;
+
+    argparse_init(&argparse, options, usages, 0);
+    argparse_describe(&argparse, NULL, g_desc_text);
+
+    argc = argparse_parse(&argparse, argc, argv);
+
+    if (help_model) {
+        printf("%s\n", g_models_text);
+
+        exit(0);
+    }
+
+    if (help_region) {
+        printf("%s\n", g_regions_text);
+
+        exit(0);
+    }
+
+    if (version) {
+        printf("%s\n", g_version_text);
+
+        exit(0);
+    }
+
+    log_set_quiet(quiet);
+
+    if (!use_args) {
+        if (!settings_path)
+            settings_path = "settings.toml";
+
+        FILE* settings = fopen(settings_path, "rb");
+
+        char error[0x100];
+
+        if (!settings) {
+            settings = fopen("settings.toml", "w+b");
+
+            if (!settings) {
+                log_error("Couldn't create settings file, loading default settings");
+
+                psxe_cfg_load_defaults(cfg);
+
+                return;
+            }
+
+            fwrite(g_default_settings, 1, sizeof(g_default_settings) - 1, settings);
+
+            fseek(settings, 0, 0);
+        }
+
+        log_info("Parsing settings file...");
+
+        toml_table_t* conf = toml_parse_file(settings, error, sizeof(error));
+
+        if (!conf) {
+            log_error("Couldn't parse settings file");
+
+            exit(1);
+        }
+
+        toml_datum_t s_version = toml_string_in(conf, "psxe_version");
+
+        if (!s_version.ok) {
+            log_error("Settings file lacking version number");
+
+            exit(1);
+        }
+
+        toml_table_t* s_bios_table = toml_table_in(conf, "bios");
+
+        if (s_bios_table) {
+            toml_datum_t s_bios_search_path = toml_string_in(s_bios_table, "search_path");
+
+            if (s_bios_search_path.ok)
+                bios_search = s_bios_search_path.u.s;
+
+            toml_datum_t s_bios_preferred_model = toml_string_in(s_bios_table, "preferred_model");
+
+            if (s_bios_preferred_model.ok)
+                model = s_bios_preferred_model.u.s;
+
+            toml_datum_t s_bios_override_file = toml_string_in(s_bios_table, "override_file");
+
+            if (s_bios_override_file.ok)
+                bios = s_bios_override_file.u.s;
+        }
+
+        toml_table_t* s_console_table = toml_table_in(conf, "console");
+
+        if (s_console_table) {
+            toml_datum_t s_console_region = toml_string_in(s_console_table, "region");
+
+            if (s_console_region.ok)
+                region = s_console_region.u.s;
+        }
+
+        psxe_version = s_version.u.s;
+
+        log_info("Settings file parsed. PSXE version: %s", psxe_version);
+
+        fclose(settings);
+    }
+
+    if (argc) {
+        if (argc > 1) {
+            log_error("Unrecognized parameter \'%s\'", argv[1]);
+
+            exit(1);
+        }
+
+        cd_path = argv[0];
+    }
+
+    if (cd_path)
+        cfg->cd_path = cd_path;
+
+    if (log_level)
+        cfg->log_level = log_level - 1;
+
+    if (bios)
+        cfg->bios = bios;
+
+    if (bios_search)
+        cfg->bios_search = bios_search;
+
+    if (model)
+        cfg->model = model;
+
+    if (exe)
+        cfg->exe = exe;
+
+    if (region)
+        cfg->region = region;
+
+    if (psxe_version)
+        cfg->psxe_version = psxe_version;
+
+    if (exp_path)
+        cfg->exp_path = exp_path;
+}
+
+// To-do: Implement BIOS searching
+char* psxe_cfg_get_bios_path(psxe_config_t* cfg) {
+    return NULL;
+}
+
+#undef STR1
 #undef STR
\ No newline at end of file
--- a/frontend/config.h
+++ b/frontend/config.h
@@ -1,37 +1,37 @@
-#ifndef CONFIG_H
-#define CONFIG_H
-
-#include <stdlib.h>
-
-#include "argparse.h"
-#include "toml.h"
-#include "psx/log.h"
-
-typedef struct {
-    int use_args;
-    int version;
-    int help_model;
-    int help_region;
-    int log_level;
-    int quiet;
-    int console_source;
-    const char* snap_path;
-    const char* settings_path;
-    const char* bios;
-    const char* bios_search;
-    const char* model;
-    const char* exe;
-    const char* region;
-    const char* psxe_version;
-    const char* cd_path;
-    const char* exp_path;
-} psxe_config_t;
-
-psxe_config_t* psxe_cfg_create();
-void psxe_cfg_init(psxe_config_t*);
-void psxe_cfg_load_defaults(psxe_config_t*);
-void psxe_cfg_load(psxe_config_t*, int, const char**);
-char* psxe_cfg_get_bios_path(psxe_config_t*);
-void psxe_cfg_destroy(psxe_config_t*);
-
+#ifndef CONFIG_H
+#define CONFIG_H
+
+#include <stdlib.h>
+
+#include "argparse.h"
+#include "toml.h"
+#include "psx/log.h"
+
+typedef struct {
+    int use_args;
+    int version;
+    int help_model;
+    int help_region;
+    int log_level;
+    int quiet;
+    int console_source;
+    const char* snap_path;
+    const char* settings_path;
+    const char* bios;
+    const char* bios_search;
+    const char* model;
+    const char* exe;
+    const char* region;
+    const char* psxe_version;
+    const char* cd_path;
+    const char* exp_path;
+} psxe_config_t;
+
+psxe_config_t* psxe_cfg_create();
+void psxe_cfg_init(psxe_config_t*);
+void psxe_cfg_load_defaults(psxe_config_t*);
+void psxe_cfg_load(psxe_config_t*, int, const char**);
+char* psxe_cfg_get_bios_path(psxe_config_t*);
+void psxe_cfg_destroy(psxe_config_t*);
+
 #endif
\ No newline at end of file
--- a/frontend/main.c
+++ b/frontend/main.c
@@ -1,121 +1,121 @@
-#include "psx/psx.h"
-#include "psx/input/sda.h"
-#include "psx/disc/cue.h"
-
-#include "screen.h"
-#include "config.h"
-
-#undef main
-
-void audio_update(void* ud, uint8_t* buf, int size) {
-    psx_cdrom_t* cdrom = ((psx_t*)ud)->cdrom;
-    psx_spu_t* spu = ((psx_t*)ud)->spu;
-
-    psx_cdrom_get_cdda_samples(cdrom, buf, size, spu);
-
-    for (int i = 0; i < (size >> 2); i++) {
-        uint32_t sample = psx_spu_get_sample(spu);
-
-        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;
-    }
-}
-
-int main(int argc, const char* argv[]) {
-    psxe_config_t* cfg = psxe_cfg_create();
-
-    psxe_cfg_init(cfg);
-    psxe_cfg_load_defaults(cfg);
-    psxe_cfg_load(cfg, argc, argv);
-
-    log_set_level(cfg->log_level);
-
-    psx_t* psx = psx_create();
-    psx_init(psx, cfg->bios, cfg->exp_path);
-
-    psx_cdrom_t* cdrom = psx_get_cdrom(psx);
-
-    if (cfg->cd_path)
-        psx_cdrom_open(cdrom, cfg->cd_path);
-
-    psxe_screen_t* screen = psxe_screen_create();
-    psxe_screen_init(screen, psx);
-    psxe_screen_set_scale(screen, 3);
-    psxe_screen_reload(screen);
-
-    SDL_Init(SDL_INIT_AUDIO);
-
-    SDL_AudioDeviceID dev;
-    SDL_AudioSpec obtained, desired;
-
-    desired.freq     = 44100;
-    desired.format   = AUDIO_S16SYS;
-    desired.channels = 2;
-    desired.samples  = CD_SECTOR_SIZE >> 2;
-    desired.callback = &audio_update;
-    desired.userdata = psx;
-
-    dev = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0);
-
-    if (dev)
-        SDL_PauseAudioDevice(dev, 0);
-    
-    psx_gpu_t* gpu = psx_get_gpu(psx);
-    psx_gpu_set_event_callback(gpu, GPU_EVENT_DMODE, psxe_gpu_dmode_event_cb);
-    psx_gpu_set_event_callback(gpu, GPU_EVENT_VBLANK, psxe_gpu_vblank_event_cb);
-    psx_gpu_set_event_callback(gpu, GPU_EVENT_HBLANK, psxe_gpu_hblank_event_cb);
-    psx_gpu_set_event_callback(gpu, GPU_EVENT_VBLANK_END, psxe_gpu_vblank_end_event_cb);
-    psx_gpu_set_event_callback(gpu, GPU_EVENT_HBLANK_END, psxe_gpu_hblank_end_event_cb);
-    psx_gpu_set_udata(gpu, 0, screen);
-    psx_gpu_set_udata(gpu, 1, psx->timer);
-
-    psx_input_t* input = psx_input_create();
-    psx_input_init(input);
-
-    psxi_sda_t* controller = psxi_sda_create();
-    psxi_sda_init(controller, SDA_MODEL_DIGITAL);
-    psxi_sda_init_input(controller, input);
-
-    psx_pad_attach_joy(psx->pad, 0, input);
-    psx_pad_attach_mcd(psx->pad, 0, "slot1.mcd");
-    psx_pad_attach_mcd(psx->pad, 1, "slot2.mcd");
-
-    if (cfg->exe) {
-        while (psx->cpu->pc != 0x80030000) {
-            psx_update(psx);
-        }
-
-        psx_load_exe(psx, cfg->exe);
-    }
-
-    psxe_cfg_destroy(cfg);
-
-    while (psxe_screen_is_open(screen)) {
-        psx_update(psx);
-    }
-
-    SDL_PauseAudioDevice(dev, 1);
-
-    psx_cpu_t* cpu = psx_get_cpu(psx);
-
-    log_set_quiet(0);
-
-    log_fatal("r0=%08x at=%08x v0=%08x v1=%08x", cpu->r[0] , cpu->r[1] , cpu->r[2] , cpu->r[3] );
-    log_fatal("a0=%08x a1=%08x a2=%08x a3=%08x", cpu->r[4] , cpu->r[5] , cpu->r[6] , cpu->r[7] );
-    log_fatal("t0=%08x t1=%08x t2=%08x t3=%08x", cpu->r[8] , cpu->r[9] , cpu->r[10], cpu->r[11]);
-    log_fatal("t4=%08x t5=%08x t6=%08x t7=%08x", cpu->r[12], cpu->r[13], cpu->r[14], cpu->r[15]);
-    log_fatal("s0=%08x s1=%08x s2=%08x s3=%08x", cpu->r[16], cpu->r[17], cpu->r[18], cpu->r[19]);
-    log_fatal("s4=%08x s5=%08x s6=%08x s7=%08x", cpu->r[20], cpu->r[21], cpu->r[22], cpu->r[23]);
-    log_fatal("t8=%08x t9=%08x k0=%08x k1=%08x", cpu->r[24], cpu->r[25], cpu->r[26], cpu->r[27]);
-    log_fatal("gp=%08x sp=%08x fp=%08x ra=%08x", cpu->r[28], cpu->r[29], cpu->r[30], cpu->r[31]);
-    log_fatal("pc=%08x hi=%08x lo=%08x ep=%08x", cpu->pc, cpu->hi, cpu->lo, cpu->cop0_r[COP0_EPC]);
-
-    psx_pad_detach_joy(psx->pad, 0);
-    psx_destroy(psx);
-    psxe_screen_destroy(screen);
-
-    return 0;
+#include "psx/psx.h"
+#include "psx/input/sda.h"
+#include "psx/disc/cue.h"
+
+#include "screen.h"
+#include "config.h"
+
+#undef main
+
+void audio_update(void* ud, uint8_t* buf, int size) {
+    psx_cdrom_t* cdrom = ((psx_t*)ud)->cdrom;
+    psx_spu_t* spu = ((psx_t*)ud)->spu;
+
+    psx_cdrom_get_cdda_samples(cdrom, buf, size, spu);
+
+    for (int i = 0; i < (size >> 2); i++) {
+        uint32_t sample = psx_spu_get_sample(spu);
+
+        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;
+    }
+}
+
+int main(int argc, const char* argv[]) {
+    psxe_config_t* cfg = psxe_cfg_create();
+
+    psxe_cfg_init(cfg);
+    psxe_cfg_load_defaults(cfg);
+    psxe_cfg_load(cfg, argc, argv);
+
+    log_set_level(cfg->log_level);
+
+    psx_t* psx = psx_create();
+    psx_init(psx, cfg->bios, cfg->exp_path);
+
+    psx_cdrom_t* cdrom = psx_get_cdrom(psx);
+
+    if (cfg->cd_path)
+        psx_cdrom_open(cdrom, cfg->cd_path);
+
+    psxe_screen_t* screen = psxe_screen_create();
+    psxe_screen_init(screen, psx);
+    psxe_screen_set_scale(screen, 3);
+    psxe_screen_reload(screen);
+
+    SDL_Init(SDL_INIT_AUDIO);
+
+    SDL_AudioDeviceID dev;
+    SDL_AudioSpec obtained, desired;
+
+    desired.freq     = 44100;
+    desired.format   = AUDIO_S16SYS;
+    desired.channels = 2;
+    desired.samples  = CD_SECTOR_SIZE >> 2;
+    desired.callback = &audio_update;
+    desired.userdata = psx;
+
+    dev = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0);
+
+    if (dev)
+        SDL_PauseAudioDevice(dev, 0);
+    
+    psx_gpu_t* gpu = psx_get_gpu(psx);
+    psx_gpu_set_event_callback(gpu, GPU_EVENT_DMODE, psxe_gpu_dmode_event_cb);
+    psx_gpu_set_event_callback(gpu, GPU_EVENT_VBLANK, psxe_gpu_vblank_event_cb);
+    psx_gpu_set_event_callback(gpu, GPU_EVENT_HBLANK, psxe_gpu_hblank_event_cb);
+    psx_gpu_set_event_callback(gpu, GPU_EVENT_VBLANK_END, psxe_gpu_vblank_end_event_cb);
+    psx_gpu_set_event_callback(gpu, GPU_EVENT_HBLANK_END, psxe_gpu_hblank_end_event_cb);
+    psx_gpu_set_udata(gpu, 0, screen);
+    psx_gpu_set_udata(gpu, 1, psx->timer);
+
+    psx_input_t* input = psx_input_create();
+    psx_input_init(input);
+
+    psxi_sda_t* controller = psxi_sda_create();
+    psxi_sda_init(controller, SDA_MODEL_DIGITAL);
+    psxi_sda_init_input(controller, input);
+
+    psx_pad_attach_joy(psx->pad, 0, input);
+    psx_pad_attach_mcd(psx->pad, 0, "slot1.mcd");
+    psx_pad_attach_mcd(psx->pad, 1, "slot2.mcd");
+
+    if (cfg->exe) {
+        while (psx->cpu->pc != 0x80030000) {
+            psx_update(psx);
+        }
+
+        psx_load_exe(psx, cfg->exe);
+    }
+
+    psxe_cfg_destroy(cfg);
+
+    while (psxe_screen_is_open(screen)) {
+        psx_update(psx);
+    }
+
+    SDL_PauseAudioDevice(dev, 1);
+
+    psx_cpu_t* cpu = psx_get_cpu(psx);
+
+    log_set_quiet(0);
+
+    log_fatal("r0=%08x at=%08x v0=%08x v1=%08x", cpu->r[0] , cpu->r[1] , cpu->r[2] , cpu->r[3] );
+    log_fatal("a0=%08x a1=%08x a2=%08x a3=%08x", cpu->r[4] , cpu->r[5] , cpu->r[6] , cpu->r[7] );
+    log_fatal("t0=%08x t1=%08x t2=%08x t3=%08x", cpu->r[8] , cpu->r[9] , cpu->r[10], cpu->r[11]);
+    log_fatal("t4=%08x t5=%08x t6=%08x t7=%08x", cpu->r[12], cpu->r[13], cpu->r[14], cpu->r[15]);
+    log_fatal("s0=%08x s1=%08x s2=%08x s3=%08x", cpu->r[16], cpu->r[17], cpu->r[18], cpu->r[19]);
+    log_fatal("s4=%08x s5=%08x s6=%08x s7=%08x", cpu->r[20], cpu->r[21], cpu->r[22], cpu->r[23]);
+    log_fatal("t8=%08x t9=%08x k0=%08x k1=%08x", cpu->r[24], cpu->r[25], cpu->r[26], cpu->r[27]);
+    log_fatal("gp=%08x sp=%08x fp=%08x ra=%08x", cpu->r[28], cpu->r[29], cpu->r[30], cpu->r[31]);
+    log_fatal("pc=%08x hi=%08x lo=%08x ep=%08x", cpu->pc, cpu->hi, cpu->lo, cpu->cop0_r[COP0_EPC]);
+
+    psx_pad_detach_joy(psx->pad, 0);
+    psx_destroy(psx);
+    psxe_screen_destroy(screen);
+
+    return 0;
 }
\ No newline at end of file
--- a/frontend/screen.c
+++ b/frontend/screen.c
@@ -1,365 +1,373 @@
-#include "screen.h"
-
-uint16_t screen_get_button(SDL_Keycode k) {
-    if (k == SDLK_x     ) return PSXI_SW_SDA_CROSS;
-    if (k == SDLK_a     ) return PSXI_SW_SDA_SQUARE;
-    if (k == SDLK_w     ) return PSXI_SW_SDA_TRIANGLE;
-    if (k == SDLK_d     ) return PSXI_SW_SDA_CIRCLE;
-    if (k == SDLK_RETURN) return PSXI_SW_SDA_START;
-    if (k == SDLK_s     ) return PSXI_SW_SDA_SELECT;
-    if (k == SDLK_UP    ) return PSXI_SW_SDA_PAD_UP;
-    if (k == SDLK_DOWN  ) return PSXI_SW_SDA_PAD_DOWN;
-    if (k == SDLK_LEFT  ) return PSXI_SW_SDA_PAD_LEFT;
-    if (k == SDLK_RIGHT ) return PSXI_SW_SDA_PAD_RIGHT;
-    if (k == SDLK_q     ) return PSXI_SW_SDA_L1;
-    if (k == SDLK_e     ) return PSXI_SW_SDA_R1;
-    if (k == SDLK_1     ) return PSXI_SW_SDA_L2;
-    if (k == SDLK_3     ) return PSXI_SW_SDA_R2;
-    if (k == SDLK_z     ) return PSXI_SW_SDA_L3;
-    if (k == SDLK_c     ) return PSXI_SW_SDA_R3;
-
-    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));
-}
-
-void psxe_screen_init(psxe_screen_t* screen, psx_t* psx) {
-    memset(screen, 0, sizeof(psxe_screen_t));
-
-    if (screen->debug_mode) {
-        screen->width = PSX_GPU_FB_WIDTH;
-        screen->height = PSX_GPU_FB_HEIGHT;
-    } else {
-        screen->width = 320;
-        screen->height = 240;
-    }
-
-    screen->scale = 1;
-    screen->open = 1;
-    screen->format = SDL_PIXELFORMAT_BGR555;
-    screen->psx = psx;
-    screen->pad = psx_get_pad(psx);
-
-    screen->texture_width = PSX_GPU_FB_WIDTH;
-    screen->texture_height = PSX_GPU_FB_HEIGHT;
-
-    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
-    SDL_SetRenderDrawColor(screen->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
-}
-
-void psxe_screen_reload(psxe_screen_t* screen) {
-    if (screen->texture) SDL_DestroyTexture(screen->texture);
-    if (screen->renderer) SDL_DestroyRenderer(screen->renderer);
-    if (screen->window) SDL_DestroyWindow(screen->window);
-
-    if (screen->debug_mode) {
-        screen->width = PSX_GPU_FB_WIDTH;
-        screen->height = PSX_GPU_FB_HEIGHT;
-    } else {
-        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(
-        "psxe " STR(REP_VERSION) "-" STR(REP_COMMIT_HASH),
-        SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
-        screen->width * screen->scale,
-        screen->height * screen->scale,
-        0
-    );
-
-    screen->renderer = SDL_CreateRenderer(
-        screen->window,
-        -1,
-        SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC
-    );
-
-    SDL_SetHint("SDL_HINT_RENDER_SCALE_QUALITY", "linear"),
-
-    screen->texture = SDL_CreateTexture(
-        screen->renderer,
-        screen->format,
-        SDL_TEXTUREACCESS_STREAMING,
-        screen->texture_width, screen->texture_height
-    );
-
-    SDL_SetTextureScaleMode(screen->texture, screen->bilinear);
-
-    // Check for retina displays
-    int width = 0, height = 0;
-
-    SDL_GetRendererOutputSize(screen->renderer, &width, &height);
-
-    if (width != (screen->width * screen->scale)) {
-        float width_scale = (float)width / (float)(screen->width * screen->scale);
-        float height_scale = (float)height / (float)(screen->height * screen->scale);
-
-        SDL_RenderSetScale(screen->renderer, width_scale, height_scale);
-    }
-
-    SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, 0);
-
-    screen->open = 1;
-}
-
-int psxe_screen_is_open(psxe_screen_t* screen) {
-    return screen->open;
-}
-
-void psxe_screen_toggle_debug_mode(psxe_screen_t* screen) {
-    screen->debug_mode = !screen->debug_mode;
-
-    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);
-}
-
-void psxe_screen_update(psxe_screen_t* screen) {
-    // void* vram = psx_get_vram(screen->psx);
-
-    // if (screen->field & 2) {
-    //     for (int y = screen->field & 1; y < 512; y += 2) {
-    //         memcpy(
-    //             ((uint8_t*)screen->buf) + (y * PSX_GPU_FB_STRIDE),
-    //             ((uint8_t*)vram) + (y * PSX_GPU_FB_STRIDE),
-    //             PSX_GPU_FB_STRIDE
-    //         );
-    //     }
-    // }
-
-    // screen->field += 1;
-    // screen->field &= 3;
-
-    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_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;
-
-    while (SDL_PollEvent(&event)) {
-        switch (event.type) {
-            case SDL_QUIT: {
-                screen->open = 0;
-            } break;
-
-            case SDL_KEYDOWN: {
-                switch (event.key.keysym.sym) {
-                    case SDLK_F1: {
-                        psxe_screen_toggle_debug_mode(screen);
-
-                        return;
-                    } break;
-
-                    case SDLK_F2: {
-                        SDL_Surface* surface = SDL_CreateRGBSurfaceWithFormat(
-                            0,
-                            screen->width,
-                            screen->height,
-                            16,
-                            SDL_PIXELFORMAT_BGR555
-                        );
-
-                        SDL_RenderReadPixels(
-                            screen->renderer,
-                            NULL,
-                            SDL_PIXELFORMAT_BGR555,
-                            surface->pixels, surface->pitch
-                        );
-
-                        SDL_SaveBMP(surface, "snap/screenshot.bmp");
-
-                        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->bilinear = !screen->bilinear;
-
-                        psxe_gpu_dmode_event_cb(screen->psx->gpu);
-                    } break;
-
-                    case SDLK_F11: {
-                        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;
-
-                    case SDLK_F5: {
-                        psx_soft_reset(screen->psx);
-                    } break;
-
-                    case SDLK_F6: {
-                        psx_swap_disc(screen->psx, ".\\roms\\Street Fighter II Movie (Japan) (Disc 2)\\Street Fighter II Movie (Japan) (Disc 2).cue");
-                    } break;
-                }
-
-                uint16_t mask = screen_get_button(event.key.keysym.sym);
-
-                psx_pad_button_press(screen->pad, 0, mask);
-
-                if (event.key.keysym.sym == SDLK_RETURN) {
-                    psx_exp2_atcons_put(screen->psx->exp2, 13);
-                }
-            } break;
-
-            case SDL_TEXTINPUT: {
-                psx_exp2_atcons_put(screen->psx->exp2, event.text.text[0]);
-            } break;
-
-            case SDL_KEYUP: {
-                uint16_t mask = screen_get_button(event.key.keysym.sym);
-
-                psx_pad_button_release(screen->pad, 0, mask);
-            } break;
-        }
-    }
-}
-
-void psxe_screen_set_scale(psxe_screen_t* screen, unsigned int scale) {
-    if (screen->debug_mode) {
-        screen->scale = 1;
-    } else {
-        screen->scale = scale;
-        screen->saved_scale = scale;
-    }
-}
-
-void psxe_screen_destroy(psxe_screen_t* screen) {
-    SDL_DestroyTexture(screen->texture);
-    SDL_DestroyRenderer(screen->renderer);
-    SDL_DestroyWindow(screen->window);
-
-    SDL_Quit();
-
-    free(screen);
-}
-
-void psxe_gpu_dmode_event_cb(psx_gpu_t* gpu) {
-    psxe_screen_t* screen = gpu->udata[0];
-
-    screen->format = psx_get_display_format(screen->psx) ?
-        SDL_PIXELFORMAT_RGB24 : SDL_PIXELFORMAT_BGR555;
-
-    if (screen->debug_mode) {
-        screen->width = PSX_GPU_FB_WIDTH;
-        screen->height = PSX_GPU_FB_HEIGHT;
-        screen->texture_width = PSX_GPU_FB_WIDTH;
-        screen->texture_height = PSX_GPU_FB_HEIGHT;
-    } else {
-        if (screen->fullscreen) {
-            SDL_DisplayMode dm;
-            SDL_GetCurrentDisplayMode(0, &dm);
-
-            screen->width = dm.w;
-            screen->height = dm.h;
-
-            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,
-        screen->format,
-        SDL_TEXTUREACCESS_STREAMING,
-        screen->texture_width, screen->texture_height
-    );
-
-    SDL_SetTextureScaleMode(screen->texture, screen->bilinear);
-
-    SDL_SetWindowSize(screen->window, screen->width, screen->height);
-}
-
-void psxe_gpu_vblank_event_cb(psx_gpu_t* gpu) {
-    psxe_screen_t* screen = gpu->udata[0];
-
-    psxe_screen_update(screen);
+#include "screen.h"
+
+uint16_t screen_get_button(SDL_Keycode k) {
+    if (k == SDLK_x     ) return PSXI_SW_SDA_CROSS;
+    if (k == SDLK_a     ) return PSXI_SW_SDA_SQUARE;
+    if (k == SDLK_w     ) return PSXI_SW_SDA_TRIANGLE;
+    if (k == SDLK_d     ) return PSXI_SW_SDA_CIRCLE;
+    if (k == SDLK_RETURN) return PSXI_SW_SDA_START;
+    if (k == SDLK_s     ) return PSXI_SW_SDA_SELECT;
+    if (k == SDLK_UP    ) return PSXI_SW_SDA_PAD_UP;
+    if (k == SDLK_DOWN  ) return PSXI_SW_SDA_PAD_DOWN;
+    if (k == SDLK_LEFT  ) return PSXI_SW_SDA_PAD_LEFT;
+    if (k == SDLK_RIGHT ) return PSXI_SW_SDA_PAD_RIGHT;
+    if (k == SDLK_q     ) return PSXI_SW_SDA_L1;
+    if (k == SDLK_e     ) return PSXI_SW_SDA_R1;
+    if (k == SDLK_1     ) return PSXI_SW_SDA_L2;
+    if (k == SDLK_3     ) return PSXI_SW_SDA_R2;
+    if (k == SDLK_z     ) return PSXI_SW_SDA_L3;
+    if (k == SDLK_c     ) return PSXI_SW_SDA_R3;
+
+    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));
+}
+
+void psxe_screen_init(psxe_screen_t* screen, psx_t* psx) {
+    memset(screen, 0, sizeof(psxe_screen_t));
+
+    if (screen->debug_mode) {
+        screen->width = PSX_GPU_FB_WIDTH;
+        screen->height = PSX_GPU_FB_HEIGHT;
+    } else {
+        screen->width = 320;
+        screen->height = 240;
+    }
+
+    screen->scale = 1;
+    screen->open = 1;
+    screen->format = SDL_PIXELFORMAT_BGR555;
+    screen->psx = psx;
+    screen->pad = psx_get_pad(psx);
+
+    screen->texture_width = PSX_GPU_FB_WIDTH;
+    screen->texture_height = PSX_GPU_FB_HEIGHT;
+
+    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
+    SDL_SetRenderDrawColor(screen->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
+}
+
+void psxe_screen_reload(psxe_screen_t* screen) {
+    if (screen->texture) SDL_DestroyTexture(screen->texture);
+    if (screen->renderer) SDL_DestroyRenderer(screen->renderer);
+    if (screen->window) SDL_DestroyWindow(screen->window);
+
+    if (screen->debug_mode) {
+        screen->width = PSX_GPU_FB_WIDTH;
+        screen->height = PSX_GPU_FB_HEIGHT;
+    } else {
+        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(
+        "psxe " STR(REP_VERSION) "-" STR(REP_COMMIT_HASH),
+        SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
+        screen->width * screen->scale,
+        screen->height * screen->scale,
+        0
+    );
+
+    screen->renderer = SDL_CreateRenderer(
+        screen->window,
+        -1,
+        SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC
+    );
+
+    SDL_SetHint("SDL_HINT_RENDER_SCALE_QUALITY", "linear"),
+
+    screen->texture = SDL_CreateTexture(
+        screen->renderer,
+        screen->format,
+        SDL_TEXTUREACCESS_STREAMING,
+        screen->texture_width, screen->texture_height
+    );
+
+    SDL_SetTextureScaleMode(screen->texture, screen->bilinear);
+
+    // Check for retina displays
+    int width = 0, height = 0;
+
+    SDL_GetRendererOutputSize(screen->renderer, &width, &height);
+
+    if (width != (screen->width * screen->scale)) {
+        float width_scale = (float)width / (float)(screen->width * screen->scale);
+        float height_scale = (float)height / (float)(screen->height * screen->scale);
+
+        SDL_RenderSetScale(screen->renderer, width_scale, height_scale);
+    }
+
+    SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, 0);
+
+    screen->open = 1;
+}
+
+int psxe_screen_is_open(psxe_screen_t* screen) {
+    return screen->open;
+}
+
+void psxe_screen_toggle_debug_mode(psxe_screen_t* screen) {
+    screen->debug_mode = !screen->debug_mode;
+
+    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);
+}
+
+// int frame = 0;
+
+void psxe_screen_update(psxe_screen_t* screen) {
+    // void* vram = psx_get_vram(screen->psx);
+
+    // if (screen->field & 2) {
+    //     for (int y = screen->field & 1; y < 512; y += 2) {
+    //         memcpy(
+    //             ((uint8_t*)screen->buf) + (y * PSX_GPU_FB_STRIDE),
+    //             ((uint8_t*)vram) + (y * PSX_GPU_FB_STRIDE),
+    //             PSX_GPU_FB_STRIDE
+    //         );
+    //     }
+    // }
+
+    // screen->field += 1;
+    // screen->field &= 3;
+
+    void* display_buf = screen->debug_mode ?
+        psx_get_vram(screen->psx) : psx_get_display_buffer(screen->psx);
+
+    // if (screen->texture_height == 240) {
+    //     display_buf = screen->psx->gpu->vram + (screen->psx->gpu->disp_x + ((frame * 256) * 1024));
+
+    //     frame ^= 1;
+    // }
+
+    SDL_UpdateTexture(screen->texture, NULL, display_buf, PSX_GPU_FB_STRIDE);
+    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;
+
+    while (SDL_PollEvent(&event)) {
+        switch (event.type) {
+            case SDL_QUIT: {
+                screen->open = 0;
+            } break;
+
+            case SDL_KEYDOWN: {
+                switch (event.key.keysym.sym) {
+                    case SDLK_F1: {
+                        psxe_screen_toggle_debug_mode(screen);
+
+                        return;
+                    } break;
+
+                    case SDLK_F2: {
+                        SDL_Surface* surface = SDL_CreateRGBSurfaceWithFormat(
+                            0,
+                            screen->width,
+                            screen->height,
+                            16,
+                            SDL_PIXELFORMAT_BGR555
+                        );
+
+                        SDL_RenderReadPixels(
+                            screen->renderer,
+                            NULL,
+                            SDL_PIXELFORMAT_BGR555,
+                            surface->pixels, surface->pitch
+                        );
+
+                        SDL_SaveBMP(surface, "snap/screenshot.bmp");
+
+                        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->bilinear = !screen->bilinear;
+
+                        psxe_gpu_dmode_event_cb(screen->psx->gpu);
+                    } break;
+
+                    case SDLK_F11: {
+                        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;
+
+                    case SDLK_F5: {
+                        psx_soft_reset(screen->psx);
+                    } break;
+
+                    case SDLK_F6: {
+                        psx_swap_disc(screen->psx, ".\\roms\\Street Fighter II Movie (Japan) (Disc 2)\\Street Fighter II Movie (Japan) (Disc 2).cue");
+                    } break;
+                }
+
+                uint16_t mask = screen_get_button(event.key.keysym.sym);
+
+                psx_pad_button_press(screen->pad, 0, mask);
+
+                if (event.key.keysym.sym == SDLK_RETURN) {
+                    psx_exp2_atcons_put(screen->psx->exp2, 13);
+                }
+            } break;
+
+            case SDL_TEXTINPUT: {
+                psx_exp2_atcons_put(screen->psx->exp2, event.text.text[0]);
+            } break;
+
+            case SDL_KEYUP: {
+                uint16_t mask = screen_get_button(event.key.keysym.sym);
+
+                psx_pad_button_release(screen->pad, 0, mask);
+            } break;
+        }
+    }
+}
+
+void psxe_screen_set_scale(psxe_screen_t* screen, unsigned int scale) {
+    if (screen->debug_mode) {
+        screen->scale = 1;
+    } else {
+        screen->scale = scale;
+        screen->saved_scale = scale;
+    }
+}
+
+void psxe_screen_destroy(psxe_screen_t* screen) {
+    SDL_DestroyTexture(screen->texture);
+    SDL_DestroyRenderer(screen->renderer);
+    SDL_DestroyWindow(screen->window);
+
+    SDL_Quit();
+
+    free(screen);
+}
+
+void psxe_gpu_dmode_event_cb(psx_gpu_t* gpu) {
+    psxe_screen_t* screen = gpu->udata[0];
+
+    screen->format = psx_get_display_format(screen->psx) ?
+        SDL_PIXELFORMAT_RGB24 : SDL_PIXELFORMAT_BGR555;
+
+    if (screen->debug_mode) {
+        screen->width = PSX_GPU_FB_WIDTH;
+        screen->height = PSX_GPU_FB_HEIGHT;
+        screen->texture_width = PSX_GPU_FB_WIDTH;
+        screen->texture_height = PSX_GPU_FB_HEIGHT;
+    } else {
+        if (screen->fullscreen) {
+            SDL_DisplayMode dm;
+            SDL_GetCurrentDisplayMode(0, &dm);
+
+            screen->width = dm.w;
+            screen->height = dm.h;
+
+            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,
+        screen->format,
+        SDL_TEXTUREACCESS_STREAMING,
+        screen->texture_width, screen->texture_height
+    );
+
+    SDL_SetTextureScaleMode(screen->texture, screen->bilinear);
+
+    SDL_SetWindowSize(screen->window, screen->width, screen->height);
+}
+
+void psxe_gpu_vblank_event_cb(psx_gpu_t* gpu) {
+    psxe_screen_t* screen = gpu->udata[0];
+
+    psxe_screen_update(screen);
 }
\ No newline at end of file
--- a/frontend/screen.h
+++ b/frontend/screen.h
@@ -1,46 +1,46 @@
-#ifndef SCREEN_H
-#define SCREEN_H
-
-#include "psx/psx.h"
-#include "common.h"
-
-#include <string.h>
-
-#include "SDL2/SDL.h"
-
-typedef struct {
-    SDL_Window* window;
-    SDL_Renderer* renderer;
-    SDL_Texture* texture;
-
-    psx_t* psx;
-    psx_pad_t* pad;
-
-    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 bilinear;
-    int fullscreen;
-    int vertical_mode;
-    int debug_mode;
-    int open;
-} psxe_screen_t;
-
-psxe_screen_t* psxe_screen_create();
-void psxe_screen_init(psxe_screen_t*, psx_t*);
-void psxe_screen_reload(psxe_screen_t*);
-int psxe_screen_is_open(psxe_screen_t*);
-void psxe_screen_update(psxe_screen_t*);
-void psxe_screen_destroy(psxe_screen_t*);
-void psxe_screen_set_scale(psxe_screen_t*, unsigned int);
-void psxe_screen_toggle_debug_mode(psxe_screen_t*);
-
-// GPU event handlers
-void psxe_gpu_dmode_event_cb(psx_gpu_t*);
-void psxe_gpu_vblank_event_cb(psx_gpu_t*);
-
+#ifndef SCREEN_H
+#define SCREEN_H
+
+#include "psx/psx.h"
+#include "common.h"
+
+#include <string.h>
+
+#include "SDL2/SDL.h"
+
+typedef struct {
+    SDL_Window* window;
+    SDL_Renderer* renderer;
+    SDL_Texture* texture;
+
+    psx_t* psx;
+    psx_pad_t* pad;
+
+    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 bilinear;
+    int fullscreen;
+    int vertical_mode;
+    int debug_mode;
+    int open;
+} psxe_screen_t;
+
+psxe_screen_t* psxe_screen_create();
+void psxe_screen_init(psxe_screen_t*, psx_t*);
+void psxe_screen_reload(psxe_screen_t*);
+int psxe_screen_is_open(psxe_screen_t*);
+void psxe_screen_update(psxe_screen_t*);
+void psxe_screen_destroy(psxe_screen_t*);
+void psxe_screen_set_scale(psxe_screen_t*, unsigned int);
+void psxe_screen_toggle_debug_mode(psxe_screen_t*);
+
+// GPU event handlers
+void psxe_gpu_dmode_event_cb(psx_gpu_t*);
+void psxe_gpu_vblank_event_cb(psx_gpu_t*);
+
 #endif
\ No newline at end of file
--- a/psx/bus.c
+++ b/psx/bus.c
@@ -1,305 +1,316 @@
-#include <stdint.h>
-#include <stdlib.h>
-
-#include "bus.h"
-#include "bus_init.h"
-#include "log.h"
-
-#define RANGE(v, s, e) ((v >= s) && (v < e))
-
-const uint32_t g_psx_bus_region_mask_table[] = {
-    0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
-    0x7fffffff, 0x1fffffff, 0xffffffff, 0xffffffff
-};
-
-psx_bus_t* psx_bus_create() {
-    return (psx_bus_t*)malloc(sizeof(psx_bus_t));
-}
-
-// Does nothing for now
-void psx_bus_init(psx_bus_t* bus) {}
-
-void psx_bus_destroy(psx_bus_t* bus) {
-    free(bus);
-}
-
-#define HANDLE_READ(dev, bits) \
-    if (RANGE(addr, bus->dev->io_base, (bus->dev->io_base + bus->dev->io_size))) { \
-        bus->access_cycles = bus->dev->bus_delay; \
-        return psx_ ## dev ## _read ## bits (bus->dev, addr - bus->dev->io_base); \
-    }
-#define HANDLE_WRITE(dev, bits) \
-    if (RANGE(addr, bus->dev->io_base, (bus->dev->io_base + bus->dev->io_size))) { \
-        bus->access_cycles = bus->dev->bus_delay; \
-        psx_ ## dev ## _write ## bits (bus->dev, addr - bus->dev->io_base, value); \
-        return; \
-    }
-
-uint32_t psx_bus_read32(psx_bus_t* bus, uint32_t addr) {
-    uint32_t vaddr = addr;
-
-    addr &= g_psx_bus_region_mask_table[addr >> 29];
-
-    if (addr & 0x3) {
-        log_fatal("Unaligned 32-bit read from %08x:%08x", vaddr, addr);
-    }
-
-    HANDLE_READ(bios, 32);
-    HANDLE_READ(ram, 32);
-    HANDLE_READ(dma, 32);
-    HANDLE_READ(exp1, 32);
-    HANDLE_READ(exp2, 32);
-    HANDLE_READ(mc1, 32);
-    HANDLE_READ(mc2, 32);
-    HANDLE_READ(mc3, 32);
-    HANDLE_READ(ic, 32);
-    HANDLE_READ(scratchpad, 32);
-    HANDLE_READ(gpu, 32);
-    HANDLE_READ(spu, 32);
-    HANDLE_READ(timer, 32);
-    HANDLE_READ(cdrom, 32);
-    HANDLE_READ(pad, 32);
-    HANDLE_READ(mdec, 32);
-
-    log_fatal("Unhandled 32-bit read from %08x:%08x", vaddr, addr);
-
-    //exit(1);
-
-    return 0x00000000;
-}
-
-uint16_t psx_bus_read16(psx_bus_t* bus, uint32_t addr) {
-    bus->access_cycles = 2;
-
-    uint32_t vaddr = addr;
-
-    addr &= g_psx_bus_region_mask_table[addr >> 29];
-
-    if (addr & 0x1) {
-        log_fatal("Unaligned 16-bit read from %08x:%08x", vaddr, addr);
-    }
-
-    HANDLE_READ(bios, 16);
-    HANDLE_READ(ram, 16);
-    HANDLE_READ(dma, 16);
-    HANDLE_READ(exp1, 16);
-    HANDLE_READ(exp2, 16);
-    HANDLE_READ(mc1, 16);
-    HANDLE_READ(mc2, 16);
-    HANDLE_READ(mc3, 16);
-    HANDLE_READ(ic, 16);
-    HANDLE_READ(scratchpad, 16);
-    HANDLE_READ(gpu, 16);
-    HANDLE_READ(spu, 16);
-    HANDLE_READ(timer, 16);
-    HANDLE_READ(cdrom, 16);
-    HANDLE_READ(pad, 16);
-    HANDLE_READ(mdec, 16);
-
-    log_fatal("Unhandled 16-bit read from %08x:%08x", vaddr, addr);
-
-    // exit(1);
-
-    return 0x0000;
-}
-
-uint8_t psx_bus_read8(psx_bus_t* bus, uint32_t addr) {
-    bus->access_cycles = 2;
-
-    uint32_t vaddr = addr;
-
-    addr &= g_psx_bus_region_mask_table[addr >> 29];
-
-    HANDLE_READ(bios, 8);
-    HANDLE_READ(ram, 8);
-    HANDLE_READ(dma, 8);
-    HANDLE_READ(exp1, 8);
-    HANDLE_READ(exp2, 8);
-    HANDLE_READ(mc1, 8);
-    HANDLE_READ(mc2, 8);
-    HANDLE_READ(mc3, 8);
-    HANDLE_READ(ic, 8);
-    HANDLE_READ(scratchpad, 8);
-    HANDLE_READ(gpu, 8);
-    HANDLE_READ(spu, 8);
-    HANDLE_READ(timer, 8);
-    HANDLE_READ(cdrom, 8);
-    HANDLE_READ(pad, 8);
-    HANDLE_READ(mdec, 8);
-
-    log_fatal("Unhandled 8-bit read from %08x:%08x", vaddr, addr);
-
-    //exit(1);
-
-    return 0x00;
-}
-
-void psx_bus_write32(psx_bus_t* bus, uint32_t addr, uint32_t value) {
-    bus->access_cycles = 0;
-
-    uint32_t vaddr = addr;
-
-    addr &= g_psx_bus_region_mask_table[addr >> 29];
-
-    if (addr & 0x3) {
-        log_fatal("Unaligned 32-bit write to %08x:%08x (%08x)", vaddr, addr, value);
-    }
-
-    HANDLE_WRITE(bios, 32);
-    HANDLE_WRITE(ram, 32);
-    HANDLE_WRITE(dma, 32);
-    HANDLE_WRITE(exp1, 32);
-    HANDLE_WRITE(exp2, 32);
-    HANDLE_WRITE(mc1, 32);
-    HANDLE_WRITE(mc2, 32);
-    HANDLE_WRITE(mc3, 32);
-    HANDLE_WRITE(ic, 32);
-    HANDLE_WRITE(scratchpad, 32);
-    HANDLE_WRITE(gpu, 32);
-    HANDLE_WRITE(spu, 32);
-    HANDLE_WRITE(timer, 32);
-    HANDLE_WRITE(cdrom, 32);
-    HANDLE_WRITE(pad, 32);
-    HANDLE_WRITE(mdec, 32);
-
-    log_fatal("Unhandled 32-bit write to %08x:%08x (%08x)", vaddr, addr, value);
-
-    //exit(1);
-}
-
-void psx_bus_write16(psx_bus_t* bus, uint32_t addr, uint16_t value) {
-    bus->access_cycles = 0;
-
-    uint32_t vaddr = addr;
-
-    addr &= g_psx_bus_region_mask_table[addr >> 29];
-
-    if (addr & 0x1) {
-        log_fatal("Unaligned 16-bit write to %08x:%08x (%04x)", vaddr, addr, value);
-    }
-
-    HANDLE_WRITE(bios, 16);
-    HANDLE_WRITE(ram, 16);
-    HANDLE_WRITE(dma, 16);
-    HANDLE_WRITE(exp1, 16);
-    HANDLE_WRITE(exp2, 16);
-    HANDLE_WRITE(mc1, 16);
-    HANDLE_WRITE(mc2, 16);
-    HANDLE_WRITE(mc3, 16);
-    HANDLE_WRITE(ic, 16);
-    HANDLE_WRITE(scratchpad, 16);
-    HANDLE_WRITE(gpu, 16);
-    HANDLE_WRITE(spu, 16);
-    HANDLE_WRITE(timer, 16);
-    HANDLE_WRITE(cdrom, 16);
-    HANDLE_WRITE(pad, 16);
-    HANDLE_WRITE(mdec, 16);
-
-    log_fatal("Unhandled 16-bit write to %08x:%08x (%04x)", vaddr, addr, value);
-
-    //exit(1);
-}
-
-void psx_bus_write8(psx_bus_t* bus, uint32_t addr, uint8_t value) {
-    bus->access_cycles = 0;
-
-    uint32_t vaddr = addr;
-
-    addr &= g_psx_bus_region_mask_table[addr >> 29];
-
-    HANDLE_WRITE(bios, 8);
-    HANDLE_WRITE(ram, 8);
-    HANDLE_WRITE(dma, 8);
-    HANDLE_WRITE(exp1, 8);
-    HANDLE_WRITE(exp2, 8);
-    HANDLE_WRITE(mc1, 8);
-    HANDLE_WRITE(mc2, 8);
-    HANDLE_WRITE(mc3, 8);
-    HANDLE_WRITE(ic, 8);
-    HANDLE_WRITE(scratchpad, 8);
-    HANDLE_WRITE(gpu, 8);
-    HANDLE_WRITE(spu, 8);
-    HANDLE_WRITE(timer, 8);
-    HANDLE_WRITE(cdrom, 8);
-    HANDLE_WRITE(pad, 8);
-    HANDLE_WRITE(mdec, 8);
-
-    log_fatal("Unhandled 8-bit write to %08x:%08x (%02x)", vaddr, addr, value);
-
-    //exit(1);
-}
-
-void psx_bus_init_bios(psx_bus_t* bus, psx_bios_t* bios) {
-    bus->bios = bios;
-}
-
-void psx_bus_init_ram(psx_bus_t* bus, psx_ram_t* ram) {
-    bus->ram = ram;
-}
-
-void psx_bus_init_dma(psx_bus_t* bus, psx_dma_t* dma) {
-    bus->dma = dma;
-}
-
-void psx_bus_init_exp1(psx_bus_t* bus, psx_exp1_t* exp1) {
-    bus->exp1 = exp1;
-}
-
-void psx_bus_init_exp2(psx_bus_t* bus, psx_exp2_t* exp2) {
-    bus->exp2 = exp2;
-}
-
-void psx_bus_init_mc1(psx_bus_t* bus, psx_mc1_t* mc1) {
-    bus->mc1 = mc1;
-}
-
-void psx_bus_init_mc2(psx_bus_t* bus, psx_mc2_t* mc2) {
-    bus->mc2 = mc2;
-}
-
-void psx_bus_init_mc3(psx_bus_t* bus, psx_mc3_t* mc3) {
-    bus->mc3 = mc3;
-}
-
-void psx_bus_init_ic(psx_bus_t* bus, psx_ic_t* ic) {
-    bus->ic = ic;
-}
-
-void psx_bus_init_scratchpad(psx_bus_t* bus, psx_scratchpad_t* scratchpad) {
-    bus->scratchpad = scratchpad;
-}
-
-void psx_bus_init_gpu(psx_bus_t* bus, psx_gpu_t* gpu) {
-    bus->gpu = gpu;
-}
-
-void psx_bus_init_spu(psx_bus_t* bus, psx_spu_t* spu) {
-    bus->spu = spu;
-}
-
-void psx_bus_init_timer(psx_bus_t* bus, psx_timer_t* timer) {
-    bus->timer = timer;
-}
-
-void psx_bus_init_cdrom(psx_bus_t* bus, psx_cdrom_t* cdrom) {
-    bus->cdrom = cdrom;
-}
-
-void psx_bus_init_pad(psx_bus_t* bus, psx_pad_t* pad) {
-    bus->pad = pad;
-}
-
-void psx_bus_init_mdec(psx_bus_t* bus, psx_mdec_t* mdec) {
-    bus->mdec = mdec;
-}
-
-uint32_t psx_bus_get_access_cycles(psx_bus_t* bus) {
-    uint32_t cycles = bus->access_cycles;
-
-    bus->access_cycles = 0;
-
-    return cycles;
-}
-
-#undef HANDLE_READ
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "bus.h"
+#include "bus_init.h"
+#include "log.h"
+
+#define RANGE(v, s, e) ((v >= s) && (v < e))
+
+const uint32_t g_psx_bus_region_mask_table[] = {
+    0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+    0x7fffffff, 0x1fffffff, 0xffffffff, 0xffffffff
+};
+
+psx_bus_t* psx_bus_create() {
+    return (psx_bus_t*)malloc(sizeof(psx_bus_t));
+}
+
+// Does nothing for now
+void psx_bus_init(psx_bus_t* bus) {}
+
+void psx_bus_destroy(psx_bus_t* bus) {
+    free(bus);
+}
+
+#define HANDLE_READ(dev, bits) \
+    if (RANGE(addr, bus->dev->io_base, (bus->dev->io_base + bus->dev->io_size))) { \
+        bus->access_cycles = bus->dev->bus_delay; \
+        return psx_ ## dev ## _read ## bits (bus->dev, addr - bus->dev->io_base); \
+    }
+#define HANDLE_WRITE(dev, bits) \
+    if (RANGE(addr, bus->dev->io_base, (bus->dev->io_base + bus->dev->io_size))) { \
+        bus->access_cycles = bus->dev->bus_delay; \
+        psx_ ## dev ## _write ## bits (bus->dev, addr - bus->dev->io_base, value); \
+        return; \
+    }
+
+uint32_t psx_bus_read32(psx_bus_t* bus, uint32_t addr) {
+    uint32_t vaddr = addr;
+
+    addr &= g_psx_bus_region_mask_table[addr >> 29];
+
+    if (addr & 0x3) {
+        log_fatal("Unaligned 32-bit read from %08x:%08x", vaddr, addr);
+    }
+
+    HANDLE_READ(bios, 32);
+    HANDLE_READ(ram, 32);
+    HANDLE_READ(dma, 32);
+    HANDLE_READ(exp1, 32);
+    HANDLE_READ(exp2, 32);
+    HANDLE_READ(mc1, 32);
+    HANDLE_READ(mc2, 32);
+    HANDLE_READ(mc3, 32);
+    HANDLE_READ(ic, 32);
+    HANDLE_READ(scratchpad, 32);
+    HANDLE_READ(gpu, 32);
+    HANDLE_READ(spu, 32);
+    HANDLE_READ(timer, 32);
+    HANDLE_READ(cdrom, 32);
+    HANDLE_READ(pad, 32);
+    HANDLE_READ(mdec, 32);
+
+    log_fatal("Unhandled 32-bit read from %08x:%08x", vaddr, addr);
+
+    //exit(1);
+
+    return 0x00000000;
+}
+
+static uint16_t sio_ctrl;
+
+uint16_t psx_bus_read16(psx_bus_t* bus, uint32_t addr) {
+    bus->access_cycles = 2;
+
+    uint32_t vaddr = addr;
+
+    addr &= g_psx_bus_region_mask_table[addr >> 29];
+
+    if (addr & 0x1) {
+        log_fatal("Unaligned 16-bit read from %08x:%08x", vaddr, addr);
+    }
+
+    HANDLE_READ(bios, 16);
+    HANDLE_READ(ram, 16);
+    HANDLE_READ(dma, 16);
+    HANDLE_READ(exp1, 16);
+    HANDLE_READ(exp2, 16);
+    HANDLE_READ(mc1, 16);
+    HANDLE_READ(mc2, 16);
+    HANDLE_READ(mc3, 16);
+    HANDLE_READ(ic, 16);
+    HANDLE_READ(scratchpad, 16);
+    HANDLE_READ(gpu, 16);
+    HANDLE_READ(spu, 16);
+    HANDLE_READ(timer, 16);
+    HANDLE_READ(cdrom, 16);
+    HANDLE_READ(pad, 16);
+    HANDLE_READ(mdec, 16);
+
+    if (addr == 0x1f80105a)
+        return sio_ctrl;
+
+    if (addr == 0x1f801054)
+        return 0x05;
+
+    printf("Unhandled 16-bit read from %08x:%08x\n", vaddr, addr);
+
+    // exit(1);
+
+    return 0x0000;
+}
+
+uint8_t psx_bus_read8(psx_bus_t* bus, uint32_t addr) {
+    bus->access_cycles = 2;
+
+    uint32_t vaddr = addr;
+
+    addr &= g_psx_bus_region_mask_table[addr >> 29];
+
+    HANDLE_READ(bios, 8);
+    HANDLE_READ(ram, 8);
+    HANDLE_READ(dma, 8);
+    HANDLE_READ(exp1, 8);
+    HANDLE_READ(exp2, 8);
+    HANDLE_READ(mc1, 8);
+    HANDLE_READ(mc2, 8);
+    HANDLE_READ(mc3, 8);
+    HANDLE_READ(ic, 8);
+    HANDLE_READ(scratchpad, 8);
+    HANDLE_READ(gpu, 8);
+    HANDLE_READ(spu, 8);
+    HANDLE_READ(timer, 8);
+    HANDLE_READ(cdrom, 8);
+    HANDLE_READ(pad, 8);
+    HANDLE_READ(mdec, 8);
+
+    // printf("Unhandled 8-bit read from %08x:%08x\n", vaddr, addr);
+
+    //exit(1);
+
+    return 0x00;
+}
+
+void psx_bus_write32(psx_bus_t* bus, uint32_t addr, uint32_t value) {
+    bus->access_cycles = 0;
+
+    uint32_t vaddr = addr;
+
+    addr &= g_psx_bus_region_mask_table[addr >> 29];
+
+    if (addr & 0x3) {
+        log_fatal("Unaligned 32-bit write to %08x:%08x (%08x)", vaddr, addr, value);
+    }
+
+    HANDLE_WRITE(bios, 32);
+    HANDLE_WRITE(ram, 32);
+    HANDLE_WRITE(dma, 32);
+    HANDLE_WRITE(exp1, 32);
+    HANDLE_WRITE(exp2, 32);
+    HANDLE_WRITE(mc1, 32);
+    HANDLE_WRITE(mc2, 32);
+    HANDLE_WRITE(mc3, 32);
+    HANDLE_WRITE(ic, 32);
+    HANDLE_WRITE(scratchpad, 32);
+    HANDLE_WRITE(gpu, 32);
+    HANDLE_WRITE(spu, 32);
+    HANDLE_WRITE(timer, 32);
+    HANDLE_WRITE(cdrom, 32);
+    HANDLE_WRITE(pad, 32);
+    HANDLE_WRITE(mdec, 32);
+
+    printf("Unhandled 32-bit write to %08x:%08x (%08x)\n", vaddr, addr, value);
+
+    //exit(1);
+}
+
+
+void psx_bus_write16(psx_bus_t* bus, uint32_t addr, uint16_t value) {
+    bus->access_cycles = 0;
+
+    uint32_t vaddr = addr;
+
+    addr &= g_psx_bus_region_mask_table[addr >> 29];
+
+    if (addr & 0x1) {
+        log_fatal("Unaligned 16-bit write to %08x:%08x (%04x)", vaddr, addr, value);
+    }
+
+    HANDLE_WRITE(bios, 16);
+    HANDLE_WRITE(ram, 16);
+    HANDLE_WRITE(dma, 16);
+    HANDLE_WRITE(exp1, 16);
+    HANDLE_WRITE(exp2, 16);
+    HANDLE_WRITE(mc1, 16);
+    HANDLE_WRITE(mc2, 16);
+    HANDLE_WRITE(mc3, 16);
+    HANDLE_WRITE(ic, 16);
+    HANDLE_WRITE(scratchpad, 16);
+    HANDLE_WRITE(gpu, 16);
+    HANDLE_WRITE(spu, 16);
+    HANDLE_WRITE(timer, 16);
+    HANDLE_WRITE(cdrom, 16);
+    HANDLE_WRITE(pad, 16);
+    HANDLE_WRITE(mdec, 16);
+
+    // if (addr == 0x1f80105a) { sio_ctrl = value; return; }
+
+    printf("Unhandled 16-bit write to %08x:%08x (%04x)\n", vaddr, addr, value);
+
+    //exit(1);
+}
+
+void psx_bus_write8(psx_bus_t* bus, uint32_t addr, uint8_t value) {
+    bus->access_cycles = 0;
+
+    uint32_t vaddr = addr;
+
+    addr &= g_psx_bus_region_mask_table[addr >> 29];
+
+    HANDLE_WRITE(bios, 8);
+    HANDLE_WRITE(ram, 8);
+    HANDLE_WRITE(dma, 8);
+    HANDLE_WRITE(exp1, 8);
+    HANDLE_WRITE(exp2, 8);
+    HANDLE_WRITE(mc1, 8);
+    HANDLE_WRITE(mc2, 8);
+    HANDLE_WRITE(mc3, 8);
+    HANDLE_WRITE(ic, 8);
+    HANDLE_WRITE(scratchpad, 8);
+    HANDLE_WRITE(gpu, 8);
+    HANDLE_WRITE(spu, 8);
+    HANDLE_WRITE(timer, 8);
+    HANDLE_WRITE(cdrom, 8);
+    HANDLE_WRITE(pad, 8);
+    HANDLE_WRITE(mdec, 8);
+
+    printf("Unhandled 8-bit write to %08x:%08x (%02x)\n", vaddr, addr, value);
+
+    //exit(1);
+}
+
+void psx_bus_init_bios(psx_bus_t* bus, psx_bios_t* bios) {
+    bus->bios = bios;
+}
+
+void psx_bus_init_ram(psx_bus_t* bus, psx_ram_t* ram) {
+    bus->ram = ram;
+}
+
+void psx_bus_init_dma(psx_bus_t* bus, psx_dma_t* dma) {
+    bus->dma = dma;
+}
+
+void psx_bus_init_exp1(psx_bus_t* bus, psx_exp1_t* exp1) {
+    bus->exp1 = exp1;
+}
+
+void psx_bus_init_exp2(psx_bus_t* bus, psx_exp2_t* exp2) {
+    bus->exp2 = exp2;
+}
+
+void psx_bus_init_mc1(psx_bus_t* bus, psx_mc1_t* mc1) {
+    bus->mc1 = mc1;
+}
+
+void psx_bus_init_mc2(psx_bus_t* bus, psx_mc2_t* mc2) {
+    bus->mc2 = mc2;
+}
+
+void psx_bus_init_mc3(psx_bus_t* bus, psx_mc3_t* mc3) {
+    bus->mc3 = mc3;
+}
+
+void psx_bus_init_ic(psx_bus_t* bus, psx_ic_t* ic) {
+    bus->ic = ic;
+}
+
+void psx_bus_init_scratchpad(psx_bus_t* bus, psx_scratchpad_t* scratchpad) {
+    bus->scratchpad = scratchpad;
+}
+
+void psx_bus_init_gpu(psx_bus_t* bus, psx_gpu_t* gpu) {
+    bus->gpu = gpu;
+}
+
+void psx_bus_init_spu(psx_bus_t* bus, psx_spu_t* spu) {
+    bus->spu = spu;
+}
+
+void psx_bus_init_timer(psx_bus_t* bus, psx_timer_t* timer) {
+    bus->timer = timer;
+}
+
+void psx_bus_init_cdrom(psx_bus_t* bus, psx_cdrom_t* cdrom) {
+    bus->cdrom = cdrom;
+}
+
+void psx_bus_init_pad(psx_bus_t* bus, psx_pad_t* pad) {
+    bus->pad = pad;
+}
+
+void psx_bus_init_mdec(psx_bus_t* bus, psx_mdec_t* mdec) {
+    bus->mdec = mdec;
+}
+
+uint32_t psx_bus_get_access_cycles(psx_bus_t* bus) {
+    uint32_t cycles = bus->access_cycles;
+
+    bus->access_cycles = 0;
+
+    return cycles;
+}
+
+#undef HANDLE_READ
 #undef HANDLE_WRITE
\ No newline at end of file
--- a/psx/bus.h
+++ b/psx/bus.h
@@ -1,21 +1,21 @@
-#ifndef BUS_H
-#define BUS_H
-
-#include <stdint.h>
-
-struct psx_bus_t;
-
-typedef struct psx_bus_t psx_bus_t;
-
-psx_bus_t* psx_bus_create();
-void psx_bus_init(psx_bus_t*);
-uint32_t psx_bus_read32(psx_bus_t*, uint32_t);
-uint16_t psx_bus_read16(psx_bus_t*, uint32_t);
-uint8_t psx_bus_read8(psx_bus_t*, uint32_t);
-void psx_bus_write32(psx_bus_t*, uint32_t, uint32_t);
-void psx_bus_write16(psx_bus_t*, uint32_t, uint16_t);
-void psx_bus_write8(psx_bus_t*, uint32_t, uint8_t);
-uint32_t psx_bus_get_access_cycles(psx_bus_t*);
-void psx_bus_destroy(psx_bus_t*);
-
+#ifndef BUS_H
+#define BUS_H
+
+#include <stdint.h>
+
+struct psx_bus_t;
+
+typedef struct psx_bus_t psx_bus_t;
+
+psx_bus_t* psx_bus_create();
+void psx_bus_init(psx_bus_t*);
+uint32_t psx_bus_read32(psx_bus_t*, uint32_t);
+uint16_t psx_bus_read16(psx_bus_t*, uint32_t);
+uint8_t psx_bus_read8(psx_bus_t*, uint32_t);
+void psx_bus_write32(psx_bus_t*, uint32_t, uint32_t);
+void psx_bus_write16(psx_bus_t*, uint32_t, uint16_t);
+void psx_bus_write8(psx_bus_t*, uint32_t, uint8_t);
+uint32_t psx_bus_get_access_cycles(psx_bus_t*);
+void psx_bus_destroy(psx_bus_t*);
+
 #endif
\ No newline at end of file
--- a/psx/bus_init.h
+++ b/psx/bus_init.h
@@ -1,59 +1,59 @@
-#ifndef BUS_INIT_H
-#define BUS_INIT_H
-
-#include "dev/bios.h"
-#include "dev/ram.h"
-#include "dev/dma.h"
-#include "dev/exp1.h"
-#include "dev/exp2.h"
-#include "dev/mc1.h"
-#include "dev/mc2.h"
-#include "dev/mc3.h"
-#include "dev/ic.h"
-#include "dev/scratchpad.h"
-#include "dev/gpu.h"
-#include "dev/spu.h"
-#include "dev/timer.h"
-#include "dev/cdrom.h"
-#include "dev/pad.h"
-#include "dev/mdec.h"
-
-struct psx_bus_t {
-    psx_bios_t* bios;
-    psx_ram_t* ram;
-    psx_dma_t* dma;
-    psx_exp1_t* exp1;
-    psx_exp2_t* exp2;
-    psx_mc1_t* mc1;
-    psx_mc2_t* mc2;
-    psx_mc3_t* mc3;
-    psx_ic_t* ic;
-    psx_scratchpad_t* scratchpad;
-    psx_gpu_t* gpu;
-    psx_spu_t* spu;
-    psx_timer_t* timer;
-    psx_cdrom_t* cdrom;
-    psx_pad_t* pad;
-    psx_mdec_t* mdec;
-
-    uint32_t access_cycles;
-};
-
-void psx_bus_init_bios(psx_bus_t*, psx_bios_t*);
-void psx_bus_init_ram(psx_bus_t*, psx_ram_t*);
-void psx_bus_init_dma(psx_bus_t*, psx_dma_t*);
-void psx_bus_init_exp1(psx_bus_t*, psx_exp1_t*);
-void psx_bus_init_exp2(psx_bus_t*, psx_exp2_t*);
-void psx_bus_init_mc1(psx_bus_t*, psx_mc1_t*);
-void psx_bus_init_mc2(psx_bus_t*, psx_mc2_t*);
-void psx_bus_init_mc3(psx_bus_t*, psx_mc3_t*);
-void psx_bus_init_ic(psx_bus_t*, psx_ic_t*);
-void psx_bus_init_scratchpad(psx_bus_t*, psx_scratchpad_t*);
-void psx_bus_init_gpu(psx_bus_t*, psx_gpu_t*);
-void psx_bus_init_spu(psx_bus_t*, psx_spu_t*);
-void psx_bus_init_timer(psx_bus_t*, psx_timer_t*);
-void psx_bus_init_cdrom(psx_bus_t*, psx_cdrom_t*);
-void psx_bus_init_pad(psx_bus_t*, psx_pad_t*);
-void psx_bus_init_mdec(psx_bus_t*, psx_mdec_t*);
-
+#ifndef BUS_INIT_H
+#define BUS_INIT_H
+
+#include "dev/bios.h"
+#include "dev/ram.h"
+#include "dev/dma.h"
+#include "dev/exp1.h"
+#include "dev/exp2.h"
+#include "dev/mc1.h"
+#include "dev/mc2.h"
+#include "dev/mc3.h"
+#include "dev/ic.h"
+#include "dev/scratchpad.h"
+#include "dev/gpu.h"
+#include "dev/spu.h"
+#include "dev/timer.h"
+#include "dev/cdrom.h"
+#include "dev/pad.h"
+#include "dev/mdec.h"
+
+struct psx_bus_t {
+    psx_bios_t* bios;
+    psx_ram_t* ram;
+    psx_dma_t* dma;
+    psx_exp1_t* exp1;
+    psx_exp2_t* exp2;
+    psx_mc1_t* mc1;
+    psx_mc2_t* mc2;
+    psx_mc3_t* mc3;
+    psx_ic_t* ic;
+    psx_scratchpad_t* scratchpad;
+    psx_gpu_t* gpu;
+    psx_spu_t* spu;
+    psx_timer_t* timer;
+    psx_cdrom_t* cdrom;
+    psx_pad_t* pad;
+    psx_mdec_t* mdec;
+
+    uint32_t access_cycles;
+};
+
+void psx_bus_init_bios(psx_bus_t*, psx_bios_t*);
+void psx_bus_init_ram(psx_bus_t*, psx_ram_t*);
+void psx_bus_init_dma(psx_bus_t*, psx_dma_t*);
+void psx_bus_init_exp1(psx_bus_t*, psx_exp1_t*);
+void psx_bus_init_exp2(psx_bus_t*, psx_exp2_t*);
+void psx_bus_init_mc1(psx_bus_t*, psx_mc1_t*);
+void psx_bus_init_mc2(psx_bus_t*, psx_mc2_t*);
+void psx_bus_init_mc3(psx_bus_t*, psx_mc3_t*);
+void psx_bus_init_ic(psx_bus_t*, psx_ic_t*);
+void psx_bus_init_scratchpad(psx_bus_t*, psx_scratchpad_t*);
+void psx_bus_init_gpu(psx_bus_t*, psx_gpu_t*);
+void psx_bus_init_spu(psx_bus_t*, psx_spu_t*);
+void psx_bus_init_timer(psx_bus_t*, psx_timer_t*);
+void psx_bus_init_cdrom(psx_bus_t*, psx_cdrom_t*);
+void psx_bus_init_pad(psx_bus_t*, psx_pad_t*);
+void psx_bus_init_mdec(psx_bus_t*, psx_mdec_t*);
+
 #endif
\ No newline at end of file
--- a/psx/config.c
+++ b/psx/config.c
@@ -1,9 +1,9 @@
-/*
-    This file should contain definitions for our
-    global configuration variables and implementations
-    for accessor methods
-*/
-
-// Suppress "empty translation unit" warning until
-// we implement core configuration
+/*
+    This file should contain definitions for our
+    global configuration variables and implementations
+    for accessor methods
+*/
+
+// Suppress "empty translation unit" warning until
+// we implement core configuration
 int dummy;
\ No newline at end of file
--- a/psx/config.h
+++ b/psx/config.h
@@ -1,8 +1,8 @@
-#ifndef CONFIG_H
-#define CONFIG_H
-
-/*
-    This file should contain accessor method definitions
-*/
-
+#ifndef CONFIG_H
+#define CONFIG_H
+
+/*
+    This file should contain accessor method definitions
+*/
+
 #endif
\ No newline at end of file
--- a/psx/cpu.c
+++ b/psx/cpu.c
@@ -1,2107 +1,2212 @@
-#include "cpu.h"
-#include "bus.h"
-#include "log.h"
-
-#include <stdlib.h>
-#include <string.h>
-
-#include "cpu_debug.h"
-
-static const psx_cpu_instruction_t g_psx_cpu_secondary_table[] = {
-    psx_cpu_i_sll    , psx_cpu_i_invalid, psx_cpu_i_srl    , psx_cpu_i_sra    ,
-    psx_cpu_i_sllv   , psx_cpu_i_invalid, psx_cpu_i_srlv   , psx_cpu_i_srav   ,
-    psx_cpu_i_jr     , psx_cpu_i_jalr   , psx_cpu_i_invalid, psx_cpu_i_invalid,
-    psx_cpu_i_syscall, psx_cpu_i_break  , psx_cpu_i_invalid, psx_cpu_i_invalid,
-    psx_cpu_i_mfhi   , psx_cpu_i_mthi   , psx_cpu_i_mflo   , psx_cpu_i_mtlo   ,
-    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
-    psx_cpu_i_mult   , psx_cpu_i_multu  , psx_cpu_i_div    , psx_cpu_i_divu   ,
-    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
-    psx_cpu_i_add    , psx_cpu_i_addu   , psx_cpu_i_sub    , psx_cpu_i_subu   ,
-    psx_cpu_i_and    , psx_cpu_i_or     , psx_cpu_i_xor    , psx_cpu_i_nor    ,
-    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_slt    , psx_cpu_i_sltu   ,
-    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
-    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
-    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
-    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
-    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid
-};
-
-static const psx_cpu_instruction_t g_psx_cpu_primary_table[] = {
-    psx_cpu_i_special, psx_cpu_i_bxx    , psx_cpu_i_j      , psx_cpu_i_jal    ,
-    psx_cpu_i_beq    , psx_cpu_i_bne    , psx_cpu_i_blez   , psx_cpu_i_bgtz   ,
-    psx_cpu_i_addi   , psx_cpu_i_addiu  , psx_cpu_i_slti   , psx_cpu_i_sltiu  ,
-    psx_cpu_i_andi   , psx_cpu_i_ori    , psx_cpu_i_xori   , psx_cpu_i_lui    ,
-    psx_cpu_i_cop0   , psx_cpu_i_cop1   , psx_cpu_i_cop2   , psx_cpu_i_cop3   ,
-    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
-    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
-    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
-    psx_cpu_i_lb     , psx_cpu_i_lh     , psx_cpu_i_lwl    , psx_cpu_i_lw     ,
-    psx_cpu_i_lbu    , psx_cpu_i_lhu    , psx_cpu_i_lwr    , psx_cpu_i_invalid,
-    psx_cpu_i_sb     , psx_cpu_i_sh     , psx_cpu_i_swl    , psx_cpu_i_sw     ,
-    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_swr    , psx_cpu_i_invalid,
-    psx_cpu_i_lwc0   , psx_cpu_i_lwc1   , psx_cpu_i_lwc2   , psx_cpu_i_lwc3   ,
-    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
-    psx_cpu_i_swc0   , psx_cpu_i_swc1   , psx_cpu_i_swc2   , psx_cpu_i_swc3   ,
-    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid
-};
-
-static const psx_cpu_instruction_t g_psx_cpu_cop0_table[] = {
-    psx_cpu_i_mfc0   , psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
-    psx_cpu_i_mtc0   , psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
-    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
-    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
-    psx_cpu_i_rfe    , psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
-    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
-    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
-    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid
-};
-
-static const psx_cpu_instruction_t g_psx_cpu_cop2_table[] = {
-    psx_cpu_i_mfc2   , psx_cpu_i_invalid, psx_cpu_i_cfc2   , psx_cpu_i_invalid,
-    psx_cpu_i_mtc2   , psx_cpu_i_invalid, psx_cpu_i_ctc2   , psx_cpu_i_invalid,
-    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
-    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
-    psx_cpu_i_gte    , psx_cpu_i_gte    , psx_cpu_i_gte    , psx_cpu_i_gte    ,
-    psx_cpu_i_gte    , psx_cpu_i_gte    , psx_cpu_i_gte    , psx_cpu_i_gte    ,
-    psx_cpu_i_gte    , psx_cpu_i_gte    , psx_cpu_i_gte    , psx_cpu_i_gte    ,
-    psx_cpu_i_gte    , psx_cpu_i_gte    , psx_cpu_i_gte    , psx_cpu_i_gte
-};
-
-static const psx_cpu_instruction_t g_psx_cpu_bxx_table[] = {
-    psx_cpu_i_bltz   , psx_cpu_i_bgez   , psx_cpu_i_bltz   , psx_cpu_i_bgez   ,
-    psx_cpu_i_bltz   , psx_cpu_i_bgez   , psx_cpu_i_bltz   , psx_cpu_i_bgez   ,
-    psx_cpu_i_bltz   , psx_cpu_i_bgez   , psx_cpu_i_bltz   , psx_cpu_i_bgez   ,
-    psx_cpu_i_bltz   , psx_cpu_i_bgez   , psx_cpu_i_bltz   , psx_cpu_i_bgez   ,
-    psx_cpu_i_bltzal , psx_cpu_i_bgezal , psx_cpu_i_bltz   , psx_cpu_i_bgez   ,
-    psx_cpu_i_bltz   , psx_cpu_i_bgez   , psx_cpu_i_bltz   , psx_cpu_i_bgez   ,
-    psx_cpu_i_bltz   , psx_cpu_i_bgez   , psx_cpu_i_bltz   , psx_cpu_i_bgez   ,
-    psx_cpu_i_bltz   , psx_cpu_i_bgez   , psx_cpu_i_bltz   , psx_cpu_i_bgez
-};
-
-static const psx_cpu_instruction_t g_psx_gte_table[] = {
-    psx_gte_i_invalid, psx_gte_i_rtps   , psx_gte_i_invalid, psx_gte_i_invalid,
-    psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_nclip  , psx_gte_i_invalid,
-    psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid,
-    psx_gte_i_op     , psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid,
-    psx_gte_i_dpcs   , psx_gte_i_intpl  , psx_gte_i_mvmva  , psx_gte_i_ncds   ,
-    psx_gte_i_cdp    , psx_gte_i_invalid, psx_gte_i_ncdt   , psx_gte_i_invalid,
-    psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_nccs   ,
-    psx_gte_i_cc     , psx_gte_i_invalid, psx_gte_i_ncs    , psx_gte_i_invalid,
-    psx_gte_i_nct    , psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid,
-    psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid,
-    psx_gte_i_sqr    , psx_gte_i_dcpl   , psx_gte_i_dpct   , psx_gte_i_invalid,
-    psx_gte_i_invalid, psx_gte_i_avsz3  , psx_gte_i_avsz4  , psx_gte_i_invalid,
-    psx_gte_i_rtpt   , psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid,
-    psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid,
-    psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid,
-    psx_gte_i_invalid, psx_gte_i_gpf    , psx_gte_i_gpl    , psx_gte_i_ncct
-};
-
-static const uint32_t g_psx_cpu_cop0_write_mask_table[] = {
-    0x00000000, // cop0r0   - N/A
-    0x00000000, // cop0r1   - N/A
-    0x00000000, // cop0r2   - N/A
-    0xffffffff, // BPC      - Breakpoint on execute (R/W)
-    0x00000000, // cop0r4   - N/A
-    0xffffffff, // BDA      - Breakpoint on data access (R/W)
-    0x00000000, // JUMPDEST - Randomly memorized jump address (R)
-    0xffc0f03f, // DCIC     - Breakpoint control (R/W)
-    0x00000000, // BadVaddr - Bad Virtual Address (R)
-    0xffffffff, // BDAM     - Data Access breakpoint mask (R/W)
-    0x00000000, // cop0r10  - N/A
-    0xffffffff, // BPCM     - Execute breakpoint mask (R/W)
-    0xffffffff, // SR       - System status register (R/W)
-    0x00000300, // CAUSE    - Describes the most recently recognised exception (R)
-    0x00000000, // EPC      - Return Address from Trap (R)
-    0x00000000  // PRID     - Processor ID (R)
-};
-
-static const uint8_t g_psx_gte_unr_table[] = {
-    0xff, 0xfd, 0xfb, 0xf9, 0xf7, 0xf5, 0xf3, 0xf1,
-    0xef, 0xee, 0xec, 0xea, 0xe8, 0xe6, 0xe4, 0xe3,
-    0xe1, 0xdf, 0xdd, 0xdc, 0xda, 0xd8, 0xd6, 0xd5,
-    0xd3, 0xd1, 0xd0, 0xce, 0xcd, 0xcb, 0xc9, 0xc8,
-    0xc6, 0xc5, 0xc3, 0xc1, 0xc0, 0xbe, 0xbd, 0xbb,
-    0xba, 0xb8, 0xb7, 0xb5, 0xb4, 0xb2, 0xb1, 0xb0,
-    0xae, 0xad, 0xab, 0xaa, 0xa9, 0xa7, 0xa6, 0xa4,
-    0xa3, 0xa2, 0xa0, 0x9f, 0x9e, 0x9c, 0x9b, 0x9a,
-    0x99, 0x97, 0x96, 0x95, 0x94, 0x92, 0x91, 0x90,
-    0x8f, 0x8d, 0x8c, 0x8b, 0x8a, 0x89, 0x87, 0x86,
-    0x85, 0x84, 0x83, 0x82, 0x81, 0x7f, 0x7e, 0x7d,
-    0x7c, 0x7b, 0x7a, 0x79, 0x78, 0x77, 0x75, 0x74,
-    0x73, 0x72, 0x71, 0x70, 0x6f, 0x6e, 0x6d, 0x6c,
-    0x6b, 0x6a, 0x69, 0x68, 0x67, 0x66, 0x65, 0x64,
-    0x63, 0x62, 0x61, 0x60, 0x5f, 0x5e, 0x5d, 0x5d,
-    0x5c, 0x5b, 0x5a, 0x59, 0x58, 0x57, 0x56, 0x55,
-    0x54, 0x53, 0x53, 0x52, 0x51, 0x50, 0x4f, 0x4e,
-    0x4d, 0x4d, 0x4c, 0x4b, 0x4a, 0x49, 0x48, 0x48,
-    0x47, 0x46, 0x45, 0x44, 0x43, 0x43, 0x42, 0x41,
-    0x40, 0x3f, 0x3f, 0x3e, 0x3d, 0x3c, 0x3c, 0x3b,
-    0x3a, 0x39, 0x39, 0x38, 0x37, 0x36, 0x36, 0x35,
-    0x34, 0x33, 0x33, 0x32, 0x31, 0x31, 0x30, 0x2f,
-    0x2e, 0x2e, 0x2d, 0x2c, 0x2c, 0x2b, 0x2a, 0x2a,
-    0x29, 0x28, 0x28, 0x27, 0x26, 0x26, 0x25, 0x24,
-    0x24, 0x23, 0x22, 0x22, 0x21, 0x20, 0x20, 0x1f,
-    0x1e, 0x1e, 0x1d, 0x1d, 0x1c, 0x1b, 0x1b, 0x1a,
-    0x19, 0x19, 0x18, 0x18, 0x17, 0x16, 0x16, 0x15,
-    0x15, 0x14, 0x14, 0x13, 0x12, 0x12, 0x11, 0x11,
-    0x10, 0x0f, 0x0f, 0x0e, 0x0e, 0x0d, 0x0d, 0x0c,
-    0x0c, 0x0b, 0x0a, 0x0a, 0x09, 0x09, 0x08, 0x08,
-    0x07, 0x07, 0x06, 0x06, 0x05, 0x05, 0x04, 0x04,
-    0x03, 0x03, 0x02, 0x02, 0x01, 0x01, 0x00, 0x00,
-    0x00
-};
-
-#define OP ((cpu->opcode >> 26) & 0x3f)
-#define S ((cpu->opcode >> 21) & 0x1f)
-#define T ((cpu->opcode >> 16) & 0x1f)
-#define D ((cpu->opcode >> 11) & 0x1f)
-#define IMM5 ((cpu->opcode >> 6) & 0x1f)
-#define CMT ((cpu->opcode >> 6) & 0xfffff)
-#define SOP (cpu->opcode & 0x3f)
-#define IMM26 (cpu->opcode & 0x3ffffff)
-#define IMM16 (cpu->opcode & 0xffff)
-#define IMM16S ((int32_t)((int16_t)IMM16))
-
-#define COP2_DR(idx) ((uint32_t*)(&cpu->cop2_dr))[idx]
-#define COP2_CR(idx) ((uint32_t*)(&cpu->cop2_cr))[idx]
-
-#define R_R0 (cpu->r[0])
-#define R_A0 (cpu->r[4])
-#define R_RA (cpu->r[31])
-
-#define DO_PENDING_LOAD \
-    cpu->r[cpu->load_d] = cpu->load_v; \
-    R_R0 = 0; \
-    cpu->load_v = 0xffffffff; \
-    cpu->load_d = 0;
-
-#define SE8(v) ((int32_t)((int8_t)v))
-#define SE16(v) ((int32_t)((int16_t)v))
-
-#define BRANCH(offset) { \
-    cpu->next_pc = cpu->next_pc + (offset); \
-    cpu->next_pc = cpu->next_pc - 4; \
-    cpu->branch = 1; \
-    cpu->branch_taken = 1; }
-
-void cpu_a_kcall_hook(psx_cpu_t* cpu) {
-    switch (cpu->r[9]) {
-        case 0x09: putc(R_A0, stdout); break;
-        case 0x3c: putchar(R_A0); break;
-        case 0x3e: {
-            uint32_t src = R_A0;
-
-            char c = psx_bus_read8(cpu->bus, src++);
-
-            while (c) {
-                putchar(c);
-
-                c = psx_bus_read8(cpu->bus, src++);
-            }
-        } break;
-    }
-}
-
-void cpu_b_kcall_hook(psx_cpu_t* cpu) {
-    switch (cpu->r[9]) {
-        case 0x3b: putc(R_A0, stdout); break;
-        case 0x3d: putchar(R_A0); break;
-        case 0x3f: {
-            uint32_t src = R_A0;
-
-            char c = psx_bus_read8(cpu->bus, src++);
-
-            while (c) {
-                putchar(c);
-
-                c = psx_bus_read8(cpu->bus, src++);
-            }
-        } break;
-    }
-}
-
-psx_cpu_t* psx_cpu_create() {
-    return (psx_cpu_t*)malloc(sizeof(psx_cpu_t));
-}
-
-void cpu_a_kcall_hook(psx_cpu_t*);
-void cpu_b_kcall_hook(psx_cpu_t*);
-
-void psx_cpu_fetch(psx_cpu_t* cpu) {
-    //cpu->buf[0] = psx_bus_read32(cpu->bus, cpu->pc);
-    //cpu->pc += 4;
-
-    // Discard fetch cycles
-    psx_bus_get_access_cycles(cpu->bus);
-}
-
-void psx_cpu_destroy(psx_cpu_t* cpu) {
-    free(cpu);
-}
-
-void psx_cpu_set_a_kcall_hook(psx_cpu_t* cpu, psx_cpu_kcall_hook_t hook) {
-    cpu->a_function_hook = hook;
-}
-
-void psx_cpu_set_b_kcall_hook(psx_cpu_t* cpu, psx_cpu_kcall_hook_t hook) {
-    cpu->b_function_hook = hook;
-}
-
-void psx_cpu_save_state(psx_cpu_t* cpu, FILE* file) {
-    fwrite((char*)cpu, sizeof(*cpu) - sizeof(psx_bus_t*), 1, file);
-}
-
-void psx_cpu_load_state(psx_cpu_t* cpu, FILE* file) {
-    if (!fread((char*)cpu, sizeof(*cpu) - sizeof(psx_bus_t*), 1, file)) {
-        perror("Error reading CPU state");
-
-        exit(1);
-    }
-}
-
-void psx_cpu_init(psx_cpu_t* cpu, psx_bus_t* bus) {
-    memset(cpu, 0, sizeof(psx_cpu_t));
-
-    psx_cpu_set_a_kcall_hook(cpu, cpu_a_kcall_hook);
-    psx_cpu_set_b_kcall_hook(cpu, cpu_b_kcall_hook);
-
-    cpu->bus = bus;
-    cpu->pc = 0xbfc00000;
-    cpu->next_pc = cpu->pc + 4;
-
-    cpu->cop0_r[COP0_SR] = 0x10900000;
-    cpu->cop0_r[COP0_PRID] = 0x00000002;
-}
-
-void psx_cpu_cycle(psx_cpu_t* cpu) {
-    cpu->last_cycles = 0;
-
-    if ((cpu->pc & 0x3fffffff) == 0x000000b4)
-        if (cpu->b_function_hook)
-            cpu->b_function_hook(cpu);
-
-    cpu->saved_pc = cpu->pc;
-    cpu->delay_slot = cpu->branch;
-    cpu->branch = 0;
-    cpu->branch_taken = 0;
-
-    if (cpu->saved_pc & 3)
-        psx_cpu_exception(cpu, CAUSE_ADEL);
-
-    cpu->opcode = psx_bus_read32(cpu->bus, cpu->pc);
-
-    cpu->pc = cpu->next_pc;
-    cpu->next_pc += 4;
-
-    if (psx_cpu_check_irq(cpu)) {
-        psx_cpu_exception(cpu, CAUSE_INT);
-
-        return;
-    }
-
-    g_psx_cpu_primary_table[OP](cpu);
-
-    // Not even trying to get precise timings here
-    cpu->last_cycles = 2;
-    cpu->total_cycles += cpu->last_cycles;
-
-    cpu->r[0] = 0;
-}
-
-int psx_cpu_check_irq(psx_cpu_t* cpu) {
-    return (cpu->cop0_r[COP0_SR] & SR_IEC) &&
-           (cpu->cop0_r[COP0_SR] & cpu->cop0_r[COP0_CAUSE] & 0x00000700);
-}
-
-void psx_cpu_exception(psx_cpu_t* cpu, uint32_t cause) {
-    // Set excode and clear 3 LSBs
-    cpu->cop0_r[COP0_CAUSE] &= 0xffffff80;
-    cpu->cop0_r[COP0_CAUSE] |= cause;
-
-    cpu->cop0_r[COP0_EPC] = cpu->saved_pc;
-
-    if (cpu->delay_slot) {
-        cpu->cop0_r[COP0_EPC] -= 4;
-        cpu->cop0_r[COP0_CAUSE] |= 0x80000000;
-    }
-
-    if ((cause == CAUSE_INT) && ((cpu->cop0_r[COP0_EPC] & 0xfe000000) == 0x4a000000))
-        cpu->cop0_r[COP0_EPC] += 4;
-
-    // Do exception stack push
-    uint32_t mode = cpu->cop0_r[COP0_SR] & 0x3f;
-
-    cpu->cop0_r[COP0_SR] &= 0xffffffc0;
-    cpu->cop0_r[COP0_SR] |= (mode << 2) & 0x3f;
-
-    // Set PC to the vector selected on BEV
-    cpu->pc = (cpu->cop0_r[COP0_SR] & SR_BEV) ? 0xbfc00180 : 0x80000080;
-
-    cpu->next_pc = cpu->pc + 4;
-}
-
-void psx_cpu_set_irq_pending(psx_cpu_t* cpu) {
-    cpu->cop0_r[COP0_CAUSE] |= SR_IM2;
-}
-
-void psx_cpu_i_invalid(psx_cpu_t* cpu) {
-    log_fatal("%08x: Illegal instruction %08x", cpu->pc - 8, cpu->opcode);
-
-    psx_cpu_exception(cpu, CAUSE_RI);
-}
-
-// Primary
-void psx_cpu_i_special(psx_cpu_t* cpu) {
-    g_psx_cpu_secondary_table[SOP](cpu);
-}
-
-void psx_cpu_i_bxx(psx_cpu_t* cpu) {
-    cpu->branch = 1;
-    cpu->branch_taken = 0;
-
-    g_psx_cpu_bxx_table[T](cpu);
-}
-
-// BXX
-void psx_cpu_i_bltz(psx_cpu_t* cpu) {
-    TRACE_B("bltz");
-
-    int32_t s = (int32_t)cpu->r[S];
-
-    DO_PENDING_LOAD;
-
-    if ((int32_t)s < (int32_t)0)
-        BRANCH(IMM16S << 2);
-}
-
-void psx_cpu_i_bgez(psx_cpu_t* cpu) {
-    TRACE_B("bgez");
-
-    int32_t s = (int32_t)cpu->r[S];
-
-    DO_PENDING_LOAD;
-
-    if ((int32_t)s >= (int32_t)0)
-        BRANCH(IMM16S << 2);
-}
-
-void psx_cpu_i_bltzal(psx_cpu_t* cpu) {
-    TRACE_B("bltzal");
-
-    int32_t s = (int32_t)cpu->r[S];
-
-    DO_PENDING_LOAD;
-
-    R_RA = cpu->next_pc;
-
-    if ((int32_t)s < (int32_t)0)
-        BRANCH(IMM16S << 2);
-}
-
-void psx_cpu_i_bgezal(psx_cpu_t* cpu) {
-    TRACE_B("bgezal");
-
-    int32_t s = (int32_t)cpu->r[S];
-
-    DO_PENDING_LOAD;
-
-    R_RA = cpu->next_pc;
-
-    if ((int32_t)s >= (int32_t)0)
-        BRANCH(IMM16S << 2);
-}
-
-void psx_cpu_i_j(psx_cpu_t* cpu) {
-    cpu->branch = 1;
-
-    TRACE_I26("j");
-
-    DO_PENDING_LOAD;
-
-    cpu->next_pc = (cpu->next_pc & 0xf0000000) | (IMM26 << 2);
-}
-
-void psx_cpu_i_jal(psx_cpu_t* cpu) {
-    cpu->branch = 1;
-
-    TRACE_I26("jal");
-
-    DO_PENDING_LOAD;
-
-    R_RA = cpu->next_pc;
-
-    cpu->next_pc = (cpu->next_pc & 0xf0000000) | (IMM26 << 2);
-}
-
-void psx_cpu_i_beq(psx_cpu_t* cpu) {
-    cpu->branch = 1;
-    cpu->branch_taken = 0;
-
-    TRACE_B("beq");
-
-    uint32_t s = cpu->r[S];
-    uint32_t t = cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    if (s == t)
-        BRANCH(IMM16S << 2);
-}
-
-void psx_cpu_i_bne(psx_cpu_t* cpu) {
-    cpu->branch = 1;
-    cpu->branch_taken = 0;
-
-    TRACE_B("bne");
-
-    uint32_t s = cpu->r[S];
-    uint32_t t = cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    if (s != t)
-        BRANCH(IMM16S << 2);
-}
-
-void psx_cpu_i_blez(psx_cpu_t* cpu) {
-    cpu->branch = 1;
-    cpu->branch_taken = 0;
-
-    TRACE_B("blez");
-
-    int32_t s = (int32_t)cpu->r[S];
-
-    DO_PENDING_LOAD;
-
-    if ((int32_t)s <= (int32_t)0)
-        BRANCH(IMM16S << 2);
-}
-
-void psx_cpu_i_bgtz(psx_cpu_t* cpu) {
-    cpu->branch = 1;
-    cpu->branch_taken = 0;
-
-    TRACE_B("bgtz");
-
-    int32_t s = (int32_t)cpu->r[S];
-
-    DO_PENDING_LOAD;
-
-    if ((int32_t)s > (int32_t)0)
-        BRANCH(IMM16S << 2);
-}
-
-void psx_cpu_i_addi(psx_cpu_t* cpu) {
-    TRACE_I16D("addi");
-
-    uint32_t s = cpu->r[S];
-
-    DO_PENDING_LOAD;
-
-    uint32_t i = IMM16S;
-    uint32_t r = s + i;
-    uint32_t o = (s ^ r) & (i ^ r);
-
-    if (o & 0x80000000) {
-        psx_cpu_exception(cpu, CAUSE_OV);
-    } else {
-        cpu->r[T] = r;
-    }
-}
-
-void psx_cpu_i_addiu(psx_cpu_t* cpu) {
-    TRACE_I16D("addiu");
-
-    uint32_t s = cpu->r[S];
-
-    DO_PENDING_LOAD;
-
-    cpu->r[T] = s + IMM16S;
-}
-
-void psx_cpu_i_slti(psx_cpu_t* cpu) {
-    TRACE_I16D("slti");
-
-    int32_t s = (int32_t)cpu->r[S];
-
-    DO_PENDING_LOAD;
-
-    cpu->r[T] = s < IMM16S;
-}
-
-void psx_cpu_i_sltiu(psx_cpu_t* cpu) {
-    TRACE_I16D("sltiu");
-
-    uint32_t s = cpu->r[S];
-
-    DO_PENDING_LOAD;
-
-    cpu->r[T] = s < IMM16S;
-}
-
-void psx_cpu_i_andi(psx_cpu_t* cpu) {
-    TRACE_I16D("andi");
-
-    uint32_t s = cpu->r[S];
-
-    DO_PENDING_LOAD;
-
-    cpu->r[T] = s & IMM16;
-}
-
-void psx_cpu_i_ori(psx_cpu_t* cpu) {
-    TRACE_I16D("ori");
-
-    uint32_t s = cpu->r[S];
-
-    DO_PENDING_LOAD;
-
-    cpu->r[T] = s | IMM16;
-}
-
-void psx_cpu_i_xori(psx_cpu_t* cpu) {
-    TRACE_I16D("xori");
-
-    uint32_t s = cpu->r[S];
-
-    DO_PENDING_LOAD;
-
-    cpu->r[T] = s ^ IMM16;
-}
-
-void psx_cpu_i_lui(psx_cpu_t* cpu) {
-    TRACE_I16S("lui");
-
-    DO_PENDING_LOAD;
-
-    cpu->r[T] = IMM16 << 16;
-}
-
-void psx_cpu_i_cop0(psx_cpu_t* cpu) {
-    g_psx_cpu_cop0_table[S](cpu);
-}
-
-void psx_cpu_i_cop1(psx_cpu_t* cpu) {
-    DO_PENDING_LOAD;
-
-    psx_cpu_exception(cpu, CAUSE_CPU);
-}
-
-void psx_cpu_i_cop2(psx_cpu_t* cpu) {
-    g_psx_cpu_cop2_table[S](cpu);
-}
-
-void psx_cpu_i_cop3(psx_cpu_t* cpu) {
-    DO_PENDING_LOAD;
-
-    psx_cpu_exception(cpu, CAUSE_CPU);
-}
-
-void psx_cpu_i_lb(psx_cpu_t* cpu) {
-    TRACE_M("lb");
-
-    uint32_t s = cpu->r[S];
-
-    DO_PENDING_LOAD;
-
-    cpu->load_d = T;
-    cpu->load_v = SE8(psx_bus_read8(cpu->bus, s + IMM16S));
-}
-
-void psx_cpu_i_lh(psx_cpu_t* cpu) {
-    TRACE_M("lh");
-
-    uint32_t s = cpu->r[S];
-
-    DO_PENDING_LOAD;
-
-    uint32_t addr = s + IMM16S;
-
-    if (addr & 0x1) {
-        psx_cpu_exception(cpu, CAUSE_ADEL);
-    } else {
-        cpu->load_d = T;
-        cpu->load_v = SE16(psx_bus_read16(cpu->bus, addr));
-    }
-}
-
-void psx_cpu_i_lwl(psx_cpu_t* cpu) {
-    TRACE_M("lwl");
-
-    uint32_t addr = cpu->r[S] + IMM16S;
-
-    uint32_t aligned = psx_bus_read32(cpu->bus, addr & ~0x3);
-
-    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;
-        case 2: cpu->load_v = (cpu->load_v & 0x000000ff) | (aligned << 8 ); break;
-        case 3: cpu->load_v =                               aligned       ; break;
-    }
-
-    cpu->load_d = T;
-}
-
-void psx_cpu_i_lw(psx_cpu_t* cpu) {
-    TRACE_M("lw");
-
-    uint32_t s = cpu->r[S];
-    uint32_t addr = s + IMM16S;
-
-    DO_PENDING_LOAD;
-
-    if (addr & 0x3) {
-        psx_cpu_exception(cpu, CAUSE_ADEL);
-    } else {
-        cpu->load_d = T;
-        cpu->load_v = psx_bus_read32(cpu->bus, addr);
-    }
-}
-
-void psx_cpu_i_lbu(psx_cpu_t* cpu) {
-    TRACE_M("lbu");
-
-    uint32_t s = cpu->r[S];
-
-    DO_PENDING_LOAD;
-
-    cpu->load_d = T;
-    cpu->load_v = psx_bus_read8(cpu->bus, s + IMM16S);
-}
-
-void psx_cpu_i_lhu(psx_cpu_t* cpu) {
-    TRACE_M("lhu");
-
-    uint32_t s = cpu->r[S];
-    uint32_t addr = s + IMM16S;
-
-    DO_PENDING_LOAD;
-
-    if (addr & 0x1) {
-        psx_cpu_exception(cpu, CAUSE_ADEL);
-    } else {
-        cpu->load_d = T;
-        cpu->load_v = psx_bus_read16(cpu->bus, addr);
-    }
-}
-
-void psx_cpu_i_lwr(psx_cpu_t* cpu) {
-    TRACE_M("lwr");
-
-    uint32_t addr = cpu->r[S] + IMM16S;
-
-    uint32_t aligned = psx_bus_read32(cpu->bus, addr & ~0x3);
-
-    switch (addr & 0x3) {
-        case 0: cpu->load_v =                               aligned       ; break;
-        case 1: cpu->load_v = (cpu->load_v & 0xff000000) | (aligned >> 8 ); break;
-        case 2: cpu->load_v = (cpu->load_v & 0xffff0000) | (aligned >> 16); break;
-        case 3: cpu->load_v = (cpu->load_v & 0xffffff00) | (aligned >> 24); break;
-    }
-
-    cpu->load_d = T;
-}
-
-void psx_cpu_i_sb(psx_cpu_t* cpu) {
-    TRACE_M("sb");
-
-    uint32_t s = cpu->r[S];
-    uint32_t t = cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    // Cache isolated
-    if (cpu->cop0_r[COP0_SR] & SR_ISC) {
-        log_debug("Ignoring write while cache is isolated");
-
-        return;
-    }
-
-    psx_bus_write8(cpu->bus, s + IMM16S, t);
-}
-
-void psx_cpu_i_sh(psx_cpu_t* cpu) {
-    TRACE_M("sh");
-
-    uint32_t s = cpu->r[S];
-    uint32_t t = cpu->r[T];
-    uint32_t addr = s + IMM16S;
-
-    DO_PENDING_LOAD;
-
-    // Cache isolated
-    if (cpu->cop0_r[COP0_SR] & SR_ISC) {
-        log_debug("Ignoring write while cache is isolated");
-
-        return;
-    }
-
-    if (addr & 0x1) {
-        psx_cpu_exception(cpu, CAUSE_ADES);
-    } else {
-        psx_bus_write16(cpu->bus, addr, t);
-    }
-}
-
-void psx_cpu_i_swl(psx_cpu_t* cpu) {
-    TRACE_M("swl");
-
-    uint32_t s = cpu->r[S];
-
-    DO_PENDING_LOAD;
-
-    uint32_t addr = s + IMM16S;
-    uint32_t aligned = addr & ~0x3;
-    uint32_t v = psx_bus_read32(cpu->bus, aligned);
-
-    switch (addr & 0x3) {
-        case 0: v = (v & 0xffffff00) | (cpu->r[T] >> 24); break;
-        case 1: v = (v & 0xffff0000) | (cpu->r[T] >> 16); break;
-        case 2: v = (v & 0xff000000) | (cpu->r[T] >> 8 ); break;
-        case 3: v =                     cpu->r[T]       ; break;
-    }
-
-    psx_bus_write32(cpu->bus, aligned, v);
-}
-
-void psx_cpu_i_sw(psx_cpu_t* cpu) {
-    TRACE_M("sw");
-
-    uint32_t s = cpu->r[S];
-    uint32_t t = cpu->r[T];
-    uint32_t addr = s + IMM16S;
-
-    DO_PENDING_LOAD;
-
-    // Cache isolated
-    if (cpu->cop0_r[COP0_SR] & SR_ISC) {
-        log_debug("Ignoring write while cache is isolated");
-
-        return;
-    }
-
-    if (addr & 0x3) {
-        psx_cpu_exception(cpu, CAUSE_ADES);
-    } else {
-        psx_bus_write32(cpu->bus, addr, t);
-    }
-}
-
-void psx_cpu_i_swr(psx_cpu_t* cpu) {
-    TRACE_M("swr");
-
-    uint32_t s = cpu->r[S];
-
-    DO_PENDING_LOAD;
-
-    uint32_t addr = s + IMM16S;
-    uint32_t aligned = addr & ~0x3;
-    uint32_t v = psx_bus_read32(cpu->bus, aligned);
-
-    switch (addr & 0x3) {
-        case 0: v =                     cpu->r[T]       ; break;
-        case 1: v = (v & 0x000000ff) | (cpu->r[T] << 8 ); break;
-        case 2: v = (v & 0x0000ffff) | (cpu->r[T] << 16); break;
-        case 3: v = (v & 0x00ffffff) | (cpu->r[T] << 24); break;
-    }
-
-    psx_bus_write32(cpu->bus, aligned, v);
-}
-
-void psx_cpu_i_lwc0(psx_cpu_t* cpu) {
-    psx_cpu_exception(cpu, CAUSE_CPU);
-}
-
-void psx_cpu_i_lwc1(psx_cpu_t* cpu) {
-    psx_cpu_exception(cpu, CAUSE_CPU);
-}
-
-void psx_cpu_i_lwc3(psx_cpu_t* cpu) {
-    psx_cpu_exception(cpu, CAUSE_CPU);
-}
-
-void psx_cpu_i_swc0(psx_cpu_t* cpu) {
-    psx_cpu_exception(cpu, CAUSE_CPU);
-}
-
-void psx_cpu_i_swc1(psx_cpu_t* cpu) {
-    psx_cpu_exception(cpu, CAUSE_CPU);
-}
-
-void psx_cpu_i_swc3(psx_cpu_t* cpu) {
-    psx_cpu_exception(cpu, CAUSE_CPU);
-}
-
-// Secondary
-void psx_cpu_i_sll(psx_cpu_t* cpu) {
-    TRACE_I5D("sll");
-
-    uint32_t t = cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    cpu->r[D] = t << IMM5;
-}
-
-void psx_cpu_i_srl(psx_cpu_t* cpu) {
-    TRACE_I5D("srl");
-
-    uint32_t t = cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    cpu->r[D] = t >> IMM5;
-}
-
-void psx_cpu_i_sra(psx_cpu_t* cpu) {
-    TRACE_I5D("sra");
-
-    int32_t t = (int32_t)cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    cpu->r[D] = t >> IMM5;
-}
-
-void psx_cpu_i_sllv(psx_cpu_t* cpu) {
-    TRACE_RT("sllv");
-
-    uint32_t s = cpu->r[S];
-    uint32_t t = cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    cpu->r[D] = t << (s & 0x1f);
-}
-
-void psx_cpu_i_srlv(psx_cpu_t* cpu) {
-    TRACE_RT("srlv");
-
-    uint32_t s = cpu->r[S];
-    uint32_t t = cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    cpu->r[D] = t >> (s & 0x1f);
-}
-
-void psx_cpu_i_srav(psx_cpu_t* cpu) {
-    TRACE_RT("srav");
-
-    uint32_t s = cpu->r[S];
-    int32_t t = (int32_t)cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    cpu->r[D] = t >> (s & 0x1f);
-}
-
-void psx_cpu_i_jr(psx_cpu_t* cpu) {
-    cpu->branch = 1;
-
-    TRACE_RS("jr");
-
-    uint32_t s = cpu->r[S];
-
-    DO_PENDING_LOAD;
-
-    cpu->next_pc = s;
-}
-
-void psx_cpu_i_jalr(psx_cpu_t* cpu) {
-    cpu->branch = 1;
-
-    TRACE_RD("jalr");
-
-    uint32_t s = cpu->r[S];
-
-    DO_PENDING_LOAD;
-
-    cpu->r[D] = cpu->next_pc;
-
-    cpu->next_pc = s;
-}
-
-void psx_cpu_i_syscall(psx_cpu_t* cpu) {
-    TRACE_I20("syscall");
-    
-    DO_PENDING_LOAD;
-
-    psx_cpu_exception(cpu, CAUSE_SYSCALL);
-}
-
-void psx_cpu_i_break(psx_cpu_t* cpu) {
-    TRACE_I20("break");
-
-    DO_PENDING_LOAD;
-
-    psx_cpu_exception(cpu, CAUSE_BP);
-}
-
-void psx_cpu_i_mfhi(psx_cpu_t* cpu) {
-    TRACE_MTF("mfhi");
-
-    DO_PENDING_LOAD;
-
-    cpu->r[D] = cpu->hi;
-}
-
-void psx_cpu_i_mthi(psx_cpu_t* cpu) {
-    TRACE_MTF("mthi");
-
-    DO_PENDING_LOAD;
-
-    cpu->hi = cpu->r[S];
-}
-
-void psx_cpu_i_mflo(psx_cpu_t* cpu) {
-    TRACE_MTF("mflo");
-
-    DO_PENDING_LOAD;
-
-    cpu->r[D] = cpu->lo;
-}
-
-void psx_cpu_i_mtlo(psx_cpu_t* cpu) {
-    TRACE_MTF("mtlo");
-
-    DO_PENDING_LOAD;
-
-    cpu->lo = cpu->r[S];
-}
-
-void psx_cpu_i_mult(psx_cpu_t* cpu) {
-    TRACE_MD("mult");
-
-    int64_t s = (int64_t)((int32_t)cpu->r[S]);
-    int64_t t = (int64_t)((int32_t)cpu->r[T]);
-
-    DO_PENDING_LOAD;
-
-    uint64_t r = s * t;
-
-    cpu->hi = r >> 32;
-    cpu->lo = r & 0xffffffff;
-}
-
-void psx_cpu_i_multu(psx_cpu_t* cpu) {
-    TRACE_MD("multu");
-
-    uint64_t s = (uint64_t)cpu->r[S];
-    uint64_t t = (uint64_t)cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    uint64_t r = s * t;
-
-    cpu->hi = r >> 32;
-    cpu->lo = r & 0xffffffff;
-}
-
-void psx_cpu_i_div(psx_cpu_t* cpu) {
-    TRACE_MD("div");
-
-    int32_t s = (int32_t)cpu->r[S];
-    int32_t t = (int32_t)cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    if (!t) {
-        cpu->hi = s;
-        cpu->lo = (s >= 0) ? 0xffffffff : 1;
-    } else if ((((uint32_t)s) == 0x80000000) && (t == -1)) {
-        cpu->hi = 0;
-        cpu->lo = 0x80000000;
-    } else {
-        cpu->hi = (uint32_t)(s % t);
-        cpu->lo = (uint32_t)(s / t);
-    }
-}
-
-void psx_cpu_i_divu(psx_cpu_t* cpu) {
-    TRACE_MD("divu");
-
-    uint32_t s = cpu->r[S];
-    uint32_t t = cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    if (!t) {
-        cpu->hi = s;
-        cpu->lo = 0xffffffff;
-    } else {
-        cpu->hi = s % t;
-        cpu->lo = s / t;
-    }
-}
-
-void psx_cpu_i_add(psx_cpu_t* cpu) {
-    TRACE_RT("add");
-
-    int32_t s = cpu->r[S];
-    int32_t t = cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    int32_t r = s + t;
-    uint32_t o = (s ^ r) & (t ^ r);
-
-    if (o & 0x80000000) {
-        psx_cpu_exception(cpu, CAUSE_OV);
-    } else {
-        cpu->r[D] = (uint32_t)r;
-    }
-}
-
-void psx_cpu_i_addu(psx_cpu_t* cpu) {
-    TRACE_RT("addu");
-
-    uint32_t s = cpu->r[S];
-    uint32_t t = cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    cpu->r[D] = s + t;
-}
-
-void psx_cpu_i_sub(psx_cpu_t* cpu) {
-    TRACE_RT("sub");
-
-    int32_t s = (int32_t)cpu->r[S];
-    int32_t t = (int32_t)cpu->r[T];
-    int32_t r;
-
-    DO_PENDING_LOAD;
-
-    int o = __builtin_ssub_overflow(s, t, &r);
-
-    if (o) {
-        psx_cpu_exception(cpu, CAUSE_OV);
-    } else {
-        cpu->r[D] = r;
-    }
-}
-
-void psx_cpu_i_subu(psx_cpu_t* cpu) {
-    TRACE_RT("subu");
-
-    uint32_t s = cpu->r[S];
-    uint32_t t = cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    cpu->r[D] = s - t;
-}
-
-void psx_cpu_i_and(psx_cpu_t* cpu) {
-    TRACE_RT("and");
-
-    uint32_t s = cpu->r[S];
-    uint32_t t = cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    cpu->r[D] = s & t;
-}
-
-void psx_cpu_i_or(psx_cpu_t* cpu) {
-    TRACE_RT("or");
-
-    uint32_t s = cpu->r[S];
-    uint32_t t = cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    cpu->r[D] = s | t;
-}
-
-void psx_cpu_i_xor(psx_cpu_t* cpu) {
-    TRACE_RT("xor");
-
-    uint32_t s = cpu->r[S];
-    uint32_t t = cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    cpu->r[D] = (s ^ t);
-}
-
-void psx_cpu_i_nor(psx_cpu_t* cpu) {
-    TRACE_RT("nor");
-
-    uint32_t s = cpu->r[S];
-    uint32_t t = cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    cpu->r[D] = ~(s | t);
-}
-
-void psx_cpu_i_slt(psx_cpu_t* cpu) {
-    TRACE_RT("slt");
-
-    int32_t s = (int32_t)cpu->r[S];
-    int32_t t = (int32_t)cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    cpu->r[D] = s < t;
-}
-
-void psx_cpu_i_sltu(psx_cpu_t* cpu) {
-    TRACE_RT("sltu");
-
-    uint32_t s = cpu->r[S];
-    uint32_t t = cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    cpu->r[D] = s < t;
-}
-
-// COP0
-void psx_cpu_i_mfc0(psx_cpu_t* cpu) {
-    TRACE_C0M("mfc0");
-
-    DO_PENDING_LOAD;
-
-    cpu->load_v = cpu->cop0_r[D];
-    cpu->load_d = T;
-}
-
-void psx_cpu_i_mtc0(psx_cpu_t* cpu) {
-    TRACE_C0M("mtc0");
-
-    uint32_t t = cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    cpu->cop0_r[D] = t & g_psx_cpu_cop0_write_mask_table[D];
-}
-
-void psx_cpu_i_rfe(psx_cpu_t* cpu) {
-    TRACE_N("rfe");
-
-    DO_PENDING_LOAD;
-
-    uint32_t mode = cpu->cop0_r[COP0_SR] & 0x3f;
-
-    cpu->cop0_r[COP0_SR] &= 0xfffffff0;
-    cpu->cop0_r[COP0_SR] |= mode >> 2;
-}
-
-// COP2
-#define MIN(a, b) (((a) < (b)) ? (a) : (b))
-#define CLAMP(v, a, b) (((v) < (a)) ? (a) : (((v) > (b)) ? (b) : (v)))
-
-void gte_handle_irgb_write(psx_cpu_t* cpu) {
-    cpu->cop2_dr.ir[1] = ((cpu->cop2_dr.irgb >> 0) & 0x1f) * 0x80;
-    cpu->cop2_dr.ir[2] = ((cpu->cop2_dr.irgb >> 5) & 0x1f) * 0x80;
-    cpu->cop2_dr.ir[3] = ((cpu->cop2_dr.irgb >> 10) & 0x1f) * 0x80;
-}
-
-void gte_handle_irgb_read(psx_cpu_t* cpu) {
-    int r = CLAMP(cpu->cop2_dr.ir[1] >> 7, 0x00, 0x1f);
-    int g = CLAMP(cpu->cop2_dr.ir[2] >> 7, 0x00, 0x1f);
-    int b = CLAMP(cpu->cop2_dr.ir[3] >> 7, 0x00, 0x1f);
-
-    cpu->cop2_dr.irgb = r | (g << 5) | (b << 10);
-}
-
-void gte_handle_sxyp_write(psx_cpu_t* cpu) {
-    cpu->cop2_dr.sxy[0] = cpu->cop2_dr.sxy[1];
-    cpu->cop2_dr.sxy[1] = cpu->cop2_dr.sxy[2];
-    cpu->cop2_dr.sxy[2] = cpu->cop2_dr.sxy[3];
-}
-
-void gte_handle_lzcs_write(psx_cpu_t* cpu) {
-    if ((cpu->cop2_dr.lzcs == 0xffffffff) || !cpu->cop2_dr.lzcs) {
-        cpu->cop2_dr.lzcr = 32;
-
-        return;
-    }
-
-    int b = (cpu->cop2_dr.lzcs >> 31) & 1;
-
-    cpu->cop2_dr.lzcr = __builtin_clz(b ? ~cpu->cop2_dr.lzcs : cpu->cop2_dr.lzcs);
-}
-
-uint32_t gte_read_register(psx_cpu_t* cpu, uint32_t r) {
-    switch (r) {
-        case 0 : return cpu->cop2_dr.v[0].xy;
-        case 1 : return (int32_t)cpu->cop2_dr.v[0].z;
-        case 2 : return cpu->cop2_dr.v[1].xy;
-        case 3 : return (int32_t)cpu->cop2_dr.v[1].z;
-        case 4 : return cpu->cop2_dr.v[2].xy;
-        case 5 : return (int32_t)cpu->cop2_dr.v[2].z;
-        case 6 : return cpu->cop2_dr.rgbc.rgbc;
-        case 7 : return cpu->cop2_dr.otz;
-        case 8 : return (int32_t)cpu->cop2_dr.ir[0];
-        case 9 : return (int32_t)cpu->cop2_dr.ir[1];
-        case 10: return (int32_t)cpu->cop2_dr.ir[2];
-        case 11: return (int32_t)cpu->cop2_dr.ir[3];
-        case 12: return cpu->cop2_dr.sxy[0].xy;
-        case 13: return cpu->cop2_dr.sxy[1].xy;
-        case 14: return cpu->cop2_dr.sxy[2].xy;
-        case 15: return cpu->cop2_dr.sxy[2].xy; // SXY2 Mirror
-        case 16: return cpu->cop2_dr.sz[0];
-        case 17: return cpu->cop2_dr.sz[1];
-        case 18: return cpu->cop2_dr.sz[2];
-        case 19: return cpu->cop2_dr.sz[3];
-        case 20: return cpu->cop2_dr.rgb[0].rgbc;
-        case 21: return cpu->cop2_dr.rgb[1].rgbc;
-        case 22: return cpu->cop2_dr.rgb[2].rgbc;
-        case 23: return cpu->cop2_dr.res1;
-        case 24: return cpu->cop2_dr.mac[0];
-        case 25: return cpu->cop2_dr.mac[1];
-        case 26: return cpu->cop2_dr.mac[2];
-        case 27: return cpu->cop2_dr.mac[3];
-        case 28: gte_handle_irgb_read(cpu); return cpu->cop2_dr.irgb;
-        case 29: return cpu->cop2_dr.irgb; // IRGB mirror
-        case 30: return cpu->cop2_dr.lzcs;
-        case 31: return cpu->cop2_dr.lzcr;
-        case 32: return cpu->cop2_cr.rt.m[0].u32;
-        case 33: return cpu->cop2_cr.rt.m[1].u32;
-        case 34: return cpu->cop2_cr.rt.m[2].u32;
-        case 35: return cpu->cop2_cr.rt.m[3].u32;
-        case 36: return (int32_t)cpu->cop2_cr.rt.m33;
-        case 37: return cpu->cop2_cr.tr.x;
-        case 38: return cpu->cop2_cr.tr.y;
-        case 39: return cpu->cop2_cr.tr.z;
-        case 40: return cpu->cop2_cr.l.m[0].u32;
-        case 41: return cpu->cop2_cr.l.m[1].u32;
-        case 42: return cpu->cop2_cr.l.m[2].u32;
-        case 43: return cpu->cop2_cr.l.m[3].u32;
-        case 44: return (int32_t)cpu->cop2_cr.l.m33;
-        case 45: return cpu->cop2_cr.bk.x;
-        case 46: return cpu->cop2_cr.bk.y;
-        case 47: return cpu->cop2_cr.bk.z;
-        case 48: return cpu->cop2_cr.lr.m[0].u32;
-        case 49: return cpu->cop2_cr.lr.m[1].u32;
-        case 50: return cpu->cop2_cr.lr.m[2].u32;
-        case 51: return cpu->cop2_cr.lr.m[3].u32;
-        case 52: return (int32_t)cpu->cop2_cr.lr.m33;
-        case 53: return cpu->cop2_cr.fc.x;
-        case 54: return cpu->cop2_cr.fc.y;
-        case 55: return cpu->cop2_cr.fc.z;
-        case 56: return cpu->cop2_cr.ofx;
-        case 57: return cpu->cop2_cr.ofy;
-        case 58: return (int32_t)(int16_t)cpu->cop2_cr.h;
-        case 59: return cpu->cop2_cr.dqa;
-        case 60: return cpu->cop2_cr.dqb;
-        case 61: return cpu->cop2_cr.zsf3;
-        case 62: return cpu->cop2_cr.zsf4;
-        case 63: return (cpu->cop2_cr.flag & 0x7ffff000) | 
-                        (((cpu->cop2_cr.flag & 0x7f87e000) != 0) << 31);
-    }
-
-    return 0x00000000;
-}
-
-void gte_write_register(psx_cpu_t* cpu, uint32_t r, uint32_t value) {
-    switch (r) {
-        case 0 : cpu->cop2_dr.v[0].xy = value; break;
-        case 1 : cpu->cop2_dr.v[0].z = value; break;
-        case 2 : cpu->cop2_dr.v[1].xy = value; break;
-        case 3 : cpu->cop2_dr.v[1].z = value; break;
-        case 4 : cpu->cop2_dr.v[2].xy = value; break;
-        case 5 : cpu->cop2_dr.v[2].z = value; break;
-        case 6 : cpu->cop2_dr.rgbc.rgbc = value; break;
-        case 7 : cpu->cop2_dr.otz = value; break;
-        case 8 : cpu->cop2_dr.ir[0] = value; break;
-        case 9 : cpu->cop2_dr.ir[1] = value; break;
-        case 10: cpu->cop2_dr.ir[2] = value; break;
-        case 11: cpu->cop2_dr.ir[3] = value; break;
-        case 12: cpu->cop2_dr.sxy[0].xy = value; break;
-        case 13: cpu->cop2_dr.sxy[1].xy = value; break;
-        case 14: cpu->cop2_dr.sxy[2].xy = value; break;
-        case 15: cpu->cop2_dr.sxy[3].xy = value; gte_handle_sxyp_write(cpu); break;
-        case 16: cpu->cop2_dr.sz[0] = value; break;
-        case 17: cpu->cop2_dr.sz[1] = value; break;
-        case 18: cpu->cop2_dr.sz[2] = value; break;
-        case 19: cpu->cop2_dr.sz[3] = value; break;
-        case 20: cpu->cop2_dr.rgb[0].rgbc = value; break;
-        case 21: cpu->cop2_dr.rgb[1].rgbc = value; break;
-        case 22: cpu->cop2_dr.rgb[2].rgbc = value; break;
-        case 23: cpu->cop2_dr.res1 = value; break;
-        case 24: cpu->cop2_dr.mac[0] = value; break;
-        case 25: cpu->cop2_dr.mac[1] = value; break;
-        case 26: cpu->cop2_dr.mac[2] = value; break;
-        case 27: cpu->cop2_dr.mac[3] = value; break;
-        case 28: cpu->cop2_dr.irgb = value & 0x7fff; gte_handle_irgb_write(cpu); break;
-        case 29: /* ORGB RO */ break;
-        case 30: cpu->cop2_dr.lzcs = value; gte_handle_lzcs_write(cpu); break;
-        case 31: /* LZCR RO */ break;
-        case 32: cpu->cop2_cr.rt.m[0].u32 = value; break;
-        case 33: cpu->cop2_cr.rt.m[1].u32 = value; break;
-        case 34: cpu->cop2_cr.rt.m[2].u32 = value; break;
-        case 35: cpu->cop2_cr.rt.m[3].u32 = value; break;
-        case 36: cpu->cop2_cr.rt.m33 = value; break;
-        case 37: cpu->cop2_cr.tr.x = value; break;
-        case 38: cpu->cop2_cr.tr.y = value; break;
-        case 39: cpu->cop2_cr.tr.z = value; break;
-        case 40: cpu->cop2_cr.l.m[0].u32 = value; break;
-        case 41: cpu->cop2_cr.l.m[1].u32 = value; break;
-        case 42: cpu->cop2_cr.l.m[2].u32 = value; break;
-        case 43: cpu->cop2_cr.l.m[3].u32 = value; break;
-        case 44: cpu->cop2_cr.l.m33 = value; break;
-        case 45: cpu->cop2_cr.bk.x = value; break;
-        case 46: cpu->cop2_cr.bk.y = value; break;
-        case 47: cpu->cop2_cr.bk.z = value; break;
-        case 48: cpu->cop2_cr.lr.m[0].u32 = value; break;
-        case 49: cpu->cop2_cr.lr.m[1].u32 = value; break;
-        case 50: cpu->cop2_cr.lr.m[2].u32 = value; break;
-        case 51: cpu->cop2_cr.lr.m[3].u32 = value; break;
-        case 52: cpu->cop2_cr.lr.m33 = value; break;
-        case 53: cpu->cop2_cr.fc.x = value; break;
-        case 54: cpu->cop2_cr.fc.y = value; break;
-        case 55: cpu->cop2_cr.fc.z = value; break;
-        case 56: cpu->cop2_cr.ofx = value; break;
-        case 57: cpu->cop2_cr.ofy = value; break;
-        case 58: cpu->cop2_cr.h = value; break;
-        case 59: cpu->cop2_cr.dqa = value; break;
-        case 60: cpu->cop2_cr.dqb = value; break;
-        case 61: cpu->cop2_cr.zsf3 = value; break;
-        case 62: cpu->cop2_cr.zsf4 = value; break;
-        case 63: cpu->cop2_cr.flag = value & 0x7ffff000; break;
-    }
-}
-
-void psx_cpu_i_lwc2(psx_cpu_t* cpu) {
-    uint32_t s = cpu->r[S];
-    uint32_t addr = s + IMM16S;
-
-    DO_PENDING_LOAD;
-
-    if (addr & 0x3) {
-        psx_cpu_exception(cpu, CAUSE_ADEL);
-    } else {
-        gte_write_register(cpu, T, psx_bus_read32(cpu->bus, addr));
-    }
-}
-
-void psx_cpu_i_swc2(psx_cpu_t* cpu) {
-    uint32_t s = cpu->r[S];
-    uint32_t addr = s + IMM16S;
-
-    DO_PENDING_LOAD;
-
-    // Cache isolated
-    if (cpu->cop0_r[COP0_SR] & SR_ISC) {
-        log_debug("Ignoring write while cache is isolated");
-
-        return;
-    }
-
-    if (addr & 0x3) {
-        psx_cpu_exception(cpu, CAUSE_ADES);
-    } else {
-        psx_bus_write32(cpu->bus, addr, gte_read_register(cpu, T));
-    }
-}
-
-void psx_cpu_i_mfc2(psx_cpu_t* cpu) {
-    TRACE_C2M("mfc2");
-
-    DO_PENDING_LOAD;
-
-    cpu->load_v = gte_read_register(cpu, D);
-    cpu->load_d = T;
-}
-
-void psx_cpu_i_cfc2(psx_cpu_t* cpu) {
-    TRACE_C2MC("cfc2");
-
-    DO_PENDING_LOAD;
-
-    cpu->load_v = gte_read_register(cpu, D + 32);
-    cpu->load_d = T;
-}
-
-void psx_cpu_i_mtc2(psx_cpu_t* cpu) {
-    TRACE_C2M("mtc2");
-
-    uint32_t t = cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    gte_write_register(cpu, D, t);
-}
-
-void psx_cpu_i_ctc2(psx_cpu_t* cpu) {
-    TRACE_C2MC("ctc2");
-
-    uint32_t t = cpu->r[T];
-
-    DO_PENDING_LOAD;
-
-    gte_write_register(cpu, D + 32, t);
-}
-
-#define R_FLAG cpu->cop2_cr.flag
-
-int64_t gte_clamp_mac0(psx_cpu_t* cpu, int64_t value) {
-    cpu->s_mac0 = value;
-
-    if (value < (-0x80000000ll)) {
-        R_FLAG |= 0x8000;
-    } else if (value > (0x7fffffffll)) {
-        R_FLAG |= 0x10000;
-    }
-
-    return value;
-}
-
-int32_t gte_clamp_mac(psx_cpu_t* cpu, int i, int64_t value) {
-    if (i == 3)
-        cpu->s_mac3 = value;
-
-    if (value < -0x80000000000) {
-        R_FLAG |= 0x8000000 >> (i - 1);
-    } else if (value > 0x7ffffffffff) {
-        R_FLAG |= 0x40000000 >> (i - 1);
-    }
-
-    return (int32_t)(((value << 20) >> 20) >> cpu->gte_sf);
-}
-
-int32_t gte_clamp_ir0(psx_cpu_t* cpu, int32_t value) {
-    if (value < 0) {
-        R_FLAG |= 0x1000;
-
-        return 0;
-    } else if (value > 0x1000) {
-        R_FLAG |= 0x1000;
-
-        return 0x1000;
-    }
-
-    return value;
-}
-
-int64_t gte_clamp_sxy(psx_cpu_t* cpu, int i, int64_t value) {
-    if (value < -0x400) {
-        R_FLAG |= (uint32_t)(0x4000 >> (i - 1));
-
-        return -0x400;
-    } else if (value > 0x3ff) {
-        R_FLAG |= (uint32_t)(0x4000 >> (i - 1));
-
-        return 0x3ff;
-    }
-
-    return value;
-}
-
-int32_t gte_clamp_sz3(psx_cpu_t* cpu, int32_t value) {
-    if (value < 0) {
-        R_FLAG |= 0x40000;
-
-        return 0;
-    } else if (value > 0xffff) {
-        R_FLAG |= 0x40000;
-
-        return 0xffff;
-    }
-
-    return value;
-}
-
-uint8_t gte_clamp_rgb(psx_cpu_t* cpu, int i, int value) {
-    if (value < 0) {
-        R_FLAG |= (uint32_t)0x200000 >> (i - 1);
-
-        return 0;
-    } else if (value > 0xff) {
-        R_FLAG |= (uint32_t)0x200000 >> (i - 1);
-
-        return 0xff;
-    }
-
-    return (uint8_t)value;
-}
-
-int32_t gte_clamp_ir(psx_cpu_t* cpu, int i, int value, int lm) {
-    if (lm && (value < 0)) {
-        R_FLAG |= (uint32_t)(0x1000000 >> (i - 1));
-
-        return 0;
-    } else if ((value < -0x8000) && !lm) {
-        R_FLAG |= (uint32_t)(0x1000000 >> (i - 1));
-
-        return -0x8000;
-    } else if (value > 0x7fff) {
-        R_FLAG |= (uint32_t)(0x1000000 >> (i - 1));
-
-        return 0x7fff;
-    }
-
-    return (int32_t)value;
-}
-
-int32_t gte_clamp_ir_z(psx_cpu_t* cpu, int64_t value, int sf, int lm) {
-    int32_t value_sf = value >> sf;
-    int32_t value_12 = value >> 12;
-    int32_t min = 0;
-
-    if (lm == 0)
-        min = -((int32_t)0x8000);
-
-    if (value_12 < (-((int32_t)0x8000)) || value_12 > 0x7fffl)
-        R_FLAG |= (1 << 22);
-
-    return (int32_t)CLAMP(value_sf, min, 0x7fffl);
-}
-
-int clz(uint32_t value) {
-    if (!value)
-        return 32;
-    
-    return __builtin_clz(value);
-}
-
-uint32_t gte_divide(psx_cpu_t* cpu, uint16_t n, uint16_t d) {
-    // Overflow
-    if (n >= d * 2) {
-        R_FLAG |= (1 << 31) | (1 << 17);
-
-        return 0x1ffff;
-    }
-
-    int shift = clz(d) - 16;
-
-    int r1 = (d << shift) & 0x7fff;
-    int r2 = g_psx_gte_unr_table[((r1 + 0x40) >> 7)] + 0x101;
-    int r3 = ((0x80 - (r2 * (r1 + 0x8000))) >> 8) & 0x1ffff;
-
-    uint32_t reciprocal = ((r2 * r3) + 0x80) >> 8;
-    uint32_t res = ((((uint64_t)reciprocal * (n << shift)) + 0x8000) >> 16);
-
-    return MIN(0x1ffff, res);
-}
-
-void psx_cpu_i_gte(psx_cpu_t* cpu) {
-    DO_PENDING_LOAD;
-
-    cpu->gte_sf = ((cpu->opcode & 0x80000) != 0) * 12;
-    cpu->gte_lm = (cpu->opcode & 0x400) != 0;
-    cpu->gte_cv = (cpu->opcode >> 13) & 3;
-    cpu->gte_v  = (cpu->opcode >> 15) & 3;
-    cpu->gte_mx = (cpu->opcode >> 17) & 3;
-
-    g_psx_gte_table[cpu->opcode & 0x3f](cpu);
-}
-
-void psx_gte_i_invalid(psx_cpu_t* cpu) {
-    log_fatal("invalid: Unimplemented GTE instruction %02x, %02x", cpu->opcode & 0x3f, cpu->opcode >> 25);
-}
-
-#define I64(v) ((int64_t)v)
-#define R_TRX cpu->cop2_cr.tr.x
-#define R_TRY cpu->cop2_cr.tr.y
-#define R_TRZ cpu->cop2_cr.tr.z
-#define R_RT11 cpu->cop2_cr.rt.m[0].c[0]
-#define R_RT11 cpu->cop2_cr.rt.m[0].c[0]
-#define R_RT12 cpu->cop2_cr.rt.m[0].c[1]
-#define R_RT13 cpu->cop2_cr.rt.m[1].c[0]
-#define R_RT21 cpu->cop2_cr.rt.m[1].c[1]
-#define R_RT22 cpu->cop2_cr.rt.m[2].c[0]
-#define R_RT23 cpu->cop2_cr.rt.m[2].c[1]
-#define R_RT31 cpu->cop2_cr.rt.m[3].c[0]
-#define R_RT32 cpu->cop2_cr.rt.m[3].c[1]
-#define R_RT33 cpu->cop2_cr.rt.m33
-#define R_MAC0 cpu->cop2_dr.mac[0]
-#define R_MAC1 cpu->cop2_dr.mac[1]
-#define R_MAC2 cpu->cop2_dr.mac[2]
-#define R_MAC3 cpu->cop2_dr.mac[3]
-#define R_OFX cpu->cop2_cr.ofx
-#define R_OFY cpu->cop2_cr.ofy
-#define R_IR0 cpu->cop2_dr.ir[0]
-#define R_IR1 cpu->cop2_dr.ir[1]
-#define R_IR2 cpu->cop2_dr.ir[2]
-#define R_IR3 cpu->cop2_dr.ir[3]
-#define R_SXY0 cpu->cop2_dr.sxy[0].xy
-#define R_SX0 cpu->cop2_dr.sxy[0].p[0]
-#define R_SY0 cpu->cop2_dr.sxy[0].p[1]
-#define R_SZ0 cpu->cop2_dr.sz[0]
-#define R_SXY1 cpu->cop2_dr.sxy[1].xy
-#define R_SX1 cpu->cop2_dr.sxy[1].p[0]
-#define R_SY1 cpu->cop2_dr.sxy[1].p[1]
-#define R_SZ1 cpu->cop2_dr.sz[1]
-#define R_SXY2 cpu->cop2_dr.sxy[2].xy
-#define R_SX2 cpu->cop2_dr.sxy[2].p[0]
-#define R_SY2 cpu->cop2_dr.sxy[2].p[1]
-#define R_SZ2 cpu->cop2_dr.sz[2]
-#define R_SZ3 cpu->cop2_dr.sz[3]
-#define R_DQA cpu->cop2_cr.dqa
-#define R_DQB cpu->cop2_cr.dqb
-#define R_ZSF3 cpu->cop2_cr.zsf3
-#define R_ZSF4 cpu->cop2_cr.zsf4
-#define R_OTZ cpu->cop2_dr.otz
-#define R_H cpu->cop2_cr.h
-#define R_RC cpu->cop2_dr.rgbc.c[0]
-#define R_GC cpu->cop2_dr.rgbc.c[1]
-#define R_BC cpu->cop2_dr.rgbc.c[2]
-#define R_CODE cpu->cop2_dr.rgbc.c[3]
-#define R_RGBC cpu->cop2_dr.rgbc.rgbc
-#define R_RFC cpu->cop2_cr.fc.x
-#define R_GFC cpu->cop2_cr.fc.y
-#define R_BFC cpu->cop2_cr.fc.z
-#define R_RGB0 cpu->cop2_dr.rgb[0].rgbc
-#define R_RGB1 cpu->cop2_dr.rgb[1].rgbc
-#define R_RGB2 cpu->cop2_dr.rgb[2].rgbc
-#define R_RC0 cpu->cop2_dr.rgb[0].c[0]
-#define R_GC0 cpu->cop2_dr.rgb[0].c[1]
-#define R_BC0 cpu->cop2_dr.rgb[0].c[2]
-#define R_CD0 cpu->cop2_dr.rgb[0].c[3]
-#define R_RC1 cpu->cop2_dr.rgb[1].c[0]
-#define R_GC1 cpu->cop2_dr.rgb[1].c[1]
-#define R_BC1 cpu->cop2_dr.rgb[1].c[2]
-#define R_CD1 cpu->cop2_dr.rgb[1].c[3]
-#define R_RC2 cpu->cop2_dr.rgb[2].c[0]
-#define R_GC2 cpu->cop2_dr.rgb[2].c[1]
-#define R_BC2 cpu->cop2_dr.rgb[2].c[2]
-#define R_CD2 cpu->cop2_dr.rgb[2].c[3]
-#define R_L11 cpu->cop2_cr.l.m[0].c[0]
-#define R_L12 cpu->cop2_cr.l.m[0].c[1]
-#define R_L13 cpu->cop2_cr.l.m[1].c[0]
-#define R_L21 cpu->cop2_cr.l.m[1].c[1]
-#define R_L22 cpu->cop2_cr.l.m[2].c[0]
-#define R_L23 cpu->cop2_cr.l.m[2].c[1]
-#define R_L31 cpu->cop2_cr.l.m[3].c[0]
-#define R_L32 cpu->cop2_cr.l.m[3].c[1]
-#define R_L33 cpu->cop2_cr.l.m33
-#define R_RBK cpu->cop2_cr.bk.x
-#define R_GBK cpu->cop2_cr.bk.y
-#define R_BBK cpu->cop2_cr.bk.z
-#define R_LR1 cpu->cop2_cr.lr.m[0].c[0]
-#define R_LR2 cpu->cop2_cr.lr.m[0].c[1]
-#define R_LR3 cpu->cop2_cr.lr.m[1].c[0]
-#define R_LG1 cpu->cop2_cr.lr.m[1].c[1]
-#define R_LG2 cpu->cop2_cr.lr.m[2].c[0]
-#define R_LG3 cpu->cop2_cr.lr.m[2].c[1]
-#define R_LB1 cpu->cop2_cr.lr.m[3].c[0]
-#define R_LB2 cpu->cop2_cr.lr.m[3].c[1]
-#define R_LB3 cpu->cop2_cr.lr.m33
-
-#define GTE_RTP_DQ(i) { \
-    R_FLAG = 0; \
-    int64_t vx = (int64_t)((int16_t)cpu->cop2_dr.v[i].p[0]); \
-    int64_t vy = (int64_t)((int16_t)cpu->cop2_dr.v[i].p[1]); \
-    int64_t vz = (int64_t)cpu->cop2_dr.v[i].z; \
-    R_MAC1 = gte_clamp_mac(cpu, 1, (((int64_t)R_TRX) << 12) + (I64((int16_t)R_RT11) * vx) + (I64((int16_t)R_RT12) * vy) + (I64((int16_t)R_RT13) * vz)); \
-    R_MAC2 = gte_clamp_mac(cpu, 2, (((int64_t)R_TRY) << 12) + (I64((int16_t)R_RT21) * vx) + (I64((int16_t)R_RT22) * vy) + (I64((int16_t)R_RT23) * vz)); \
-    R_MAC3 = gte_clamp_mac(cpu, 3, (((int64_t)R_TRZ) << 12) + (I64((int16_t)R_RT31) * vx) + (I64((int16_t)R_RT32) * vy) + (I64((int16_t)R_RT33) * vz)); \
-    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm); \
-    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm); \
-    R_IR3 = gte_clamp_ir_z(cpu, cpu->s_mac3, cpu->gte_sf, cpu->gte_lm); \
-    R_SZ0 = R_SZ1; \
-    R_SZ1 = R_SZ2; \
-    R_SZ2 = R_SZ3; \
-    R_SZ3 = gte_clamp_sz3(cpu, cpu->s_mac3 >> 12); \
-    int32_t div = gte_divide(cpu, R_H, R_SZ3); \
-    R_SXY0 = R_SXY1; \
-    R_SXY1 = R_SXY2; \
-    R_SX2 = gte_clamp_sxy(cpu, 1, (gte_clamp_mac0(cpu, (int64_t)((int32_t)R_OFX) + ((int64_t)R_IR1 * div)) >> 16)); \
-    R_SY2 = gte_clamp_sxy(cpu, 2, (gte_clamp_mac0(cpu, (int64_t)((int32_t)R_OFY) + ((int64_t)R_IR2 * div)) >> 16)); \
-    R_MAC0 = gte_clamp_mac0(cpu, ((int64_t)R_DQB) + (((int64_t)R_DQA) * div)); \
-    R_IR0 = gte_clamp_ir0(cpu, cpu->s_mac0 >> 12); }
-
-#define GTE_RTP(i) { \
-    R_FLAG = 0; \
-    int64_t vx = (int64_t)((int16_t)cpu->cop2_dr.v[i].p[0]); \
-    int64_t vy = (int64_t)((int16_t)cpu->cop2_dr.v[i].p[1]); \
-    int64_t vz = (int64_t)cpu->cop2_dr.v[i].z; \
-    R_MAC1 = gte_clamp_mac(cpu, 1, (((int64_t)R_TRX) << 12) + (I64((int16_t)R_RT11) * vx) + (I64((int16_t)R_RT12) * vy) + (I64((int16_t)R_RT13) * vz)); \
-    R_MAC2 = gte_clamp_mac(cpu, 2, (((int64_t)R_TRY) << 12) + (I64((int16_t)R_RT21) * vx) + (I64((int16_t)R_RT22) * vy) + (I64((int16_t)R_RT23) * vz)); \
-    R_MAC3 = gte_clamp_mac(cpu, 3, (((int64_t)R_TRZ) << 12) + (I64((int16_t)R_RT31) * vx) + (I64((int16_t)R_RT32) * vy) + (I64((int16_t)R_RT33) * vz)); \
-    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm); \
-    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm); \
-    R_IR3 = gte_clamp_ir_z(cpu, cpu->s_mac3, cpu->gte_sf, cpu->gte_lm); \
-    R_SZ0 = R_SZ1; \
-    R_SZ1 = R_SZ2; \
-    R_SZ2 = R_SZ3; \
-    R_SZ3 = gte_clamp_sz3(cpu, cpu->s_mac3 >> 12); \
-    int32_t div = gte_divide(cpu, R_H, R_SZ3); \
-    R_SXY0 = R_SXY1; \
-    R_SXY1 = R_SXY2; \
-    R_SX2 = gte_clamp_sxy(cpu, 1, (gte_clamp_mac0(cpu, (int64_t)((int32_t)R_OFX) + ((int64_t)R_IR1 * div)) >> 16)); \
-    R_SY2 = gte_clamp_sxy(cpu, 2, (gte_clamp_mac0(cpu, (int64_t)((int32_t)R_OFY) + ((int64_t)R_IR2 * div)) >> 16)); }
-
-void psx_gte_i_rtps(psx_cpu_t* cpu) {
-    GTE_RTP_DQ(0);
-}
-
-void psx_gte_i_nclip(psx_cpu_t* cpu) {
-    R_FLAG = 0;
-
-    int64_t value = I64((int16_t)R_SX0) * (I64((int16_t)R_SY1) - I64((int16_t)R_SY2));
-    value += I64((int16_t)R_SX1) * (I64((int16_t)R_SY2) - I64((int16_t)R_SY0));
-    value += I64((int16_t)R_SX2) * (I64((int16_t)R_SY0) - I64((int16_t)R_SY1));
-
-    R_MAC0 = (int)gte_clamp_mac0(cpu, value);
-}
-
-void psx_gte_i_op(psx_cpu_t* cpu) {
-    R_FLAG = 0;
-
-    R_MAC1 = gte_clamp_mac(cpu, 1, I64(I64((int16_t)R_RT22) * I64(R_IR3)) - I64((I64((int16_t)R_RT33) * I64(R_IR2))));
-    R_MAC2 = gte_clamp_mac(cpu, 2, I64(I64((int16_t)R_RT33) * I64(R_IR1)) - I64((I64((int16_t)R_RT11) * I64(R_IR3))));
-    R_MAC3 = gte_clamp_mac(cpu, 3, I64(I64((int16_t)R_RT11) * I64(R_IR2)) - I64((I64((int16_t)R_RT22) * I64(R_IR1))));
-
-    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
-    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
-    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
-}
-
-void psx_gte_i_dpcs(psx_cpu_t* cpu) {
-    R_FLAG = 0;
-
-    int64_t mac1 = gte_clamp_mac(cpu, 1, (((int64_t)R_RFC) << 12) - (((int64_t)R_RC) << 16));
-    int64_t mac2 = gte_clamp_mac(cpu, 2, (((int64_t)R_GFC) << 12) - (((int64_t)R_GC) << 16));
-    int64_t mac3 = gte_clamp_mac(cpu, 3, (((int64_t)R_BFC) << 12) - (((int64_t)R_BC) << 16));
-
-    int64_t ir1 = gte_clamp_ir(cpu, 1, mac1, 0);
-    int64_t ir2 = gte_clamp_ir(cpu, 2, mac2, 0);
-    int64_t ir3 = gte_clamp_ir(cpu, 3, mac3, 0);
-
-    R_MAC1 = gte_clamp_mac(cpu, 1, (((int64_t)R_RC) << 16) + (R_IR0 * ir1));
-    R_MAC2 = gte_clamp_mac(cpu, 2, (((int64_t)R_GC) << 16) + (R_IR0 * ir2));
-    R_MAC3 = gte_clamp_mac(cpu, 3, (((int64_t)R_BC) << 16) + (R_IR0 * ir3));
-
-    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
-    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
-    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
-
-    R_RGB0 = R_RGB1;
-    R_RGB1 = R_RGB2;
-    R_CD2 = R_CODE;
-
-    R_RC2 = gte_clamp_rgb(cpu, 1, R_MAC1 >> 4);
-    R_GC2 = gte_clamp_rgb(cpu, 2, R_MAC2 >> 4);
-    R_BC2 = gte_clamp_rgb(cpu, 3, R_MAC3 >> 4);
-}
-
-void psx_gte_i_intpl(psx_cpu_t* cpu) {
-    R_FLAG = 0;
-
-    int64_t mac1 = gte_clamp_mac(cpu, 1, (((int64_t)R_RFC) << 12) - (I64(R_IR1) << 12));
-    int64_t mac2 = gte_clamp_mac(cpu, 2, (((int64_t)R_GFC) << 12) - (I64(R_IR2) << 12));
-    int64_t mac3 = gte_clamp_mac(cpu, 3, (((int64_t)R_BFC) << 12) - (I64(R_IR3) << 12));
-
-    int64_t ir1 = gte_clamp_ir(cpu, 1, mac1, 0);
-    int64_t ir2 = gte_clamp_ir(cpu, 2, mac2, 0);
-    int64_t ir3 = gte_clamp_ir(cpu, 3, mac3, 0);
-
-    R_MAC1 = gte_clamp_mac(cpu, 1, (I64(R_IR1) << 12) + (I64(R_IR0) * ir1));
-    R_MAC2 = gte_clamp_mac(cpu, 2, (I64(R_IR2) << 12) + (I64(R_IR0) * ir2));
-    R_MAC3 = gte_clamp_mac(cpu, 3, (I64(R_IR3) << 12) + (I64(R_IR0) * ir3));
-
-    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
-    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
-    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
-
-    R_RGB0 = R_RGB1;
-    R_RGB1 = R_RGB2;
-    R_CD2 = R_CODE;
-
-    R_RC2 = gte_clamp_rgb(cpu, 1, R_MAC1 >> 4);
-    R_GC2 = gte_clamp_rgb(cpu, 2, R_MAC2 >> 4);
-    R_BC2 = gte_clamp_rgb(cpu, 3, R_MAC3 >> 4);
-}
-
-#define R_VX v.p[0]
-#define R_VY v.p[1]
-#define R_VZ v.z
-#define R_MX11 mx.m[0].c[0]
-#define R_MX11 mx.m[0].c[0]
-#define R_MX12 mx.m[0].c[1]
-#define R_MX13 mx.m[1].c[0]
-#define R_MX21 mx.m[1].c[1]
-#define R_MX22 mx.m[2].c[0]
-#define R_MX23 mx.m[2].c[1]
-#define R_MX31 mx.m[3].c[0]
-#define R_MX32 mx.m[3].c[1]
-#define R_MX33 mx.m33
-#define R_CV1 cv.x
-#define R_CV2 cv.y
-#define R_CV3 cv.z
-
-void psx_gte_i_mvmva(psx_cpu_t* cpu) {
-    R_FLAG = 0;
-
-    gte_matrix_t mx = { 0 };
-    gte_vertex_t v = { 0 };
-    gte_vec3_t cv = { 0 };
-
-    switch (cpu->gte_mx) {
-        case 0: mx = cpu->cop2_cr.rt; break;
-        case 1: mx = cpu->cop2_cr.l; break;
-        case 2: mx = cpu->cop2_cr.lr; break;
-        case 3: {
-            R_MX11 = -R_RC << 4;
-            R_MX12 = R_RC << 4;
-            R_MX13 = R_IR0;
-            R_MX21 = R_RT13;
-            R_MX22 = R_RT13;
-            R_MX23 = R_RT13;
-            R_MX31 = R_RT22;
-            R_MX32 = R_RT22;
-            R_MX33 = R_RT22;
-        } break;
-    }
-
-    switch (cpu->gte_v) {
-        case 0: case 1: case 2:
-            v = cpu->cop2_dr.v[cpu->gte_v];
-        break;
-
-        case 3: {
-            v.p[0] = R_IR1;
-            v.p[1] = R_IR2;
-            v.z = R_IR3;
-        } break;
-    }
-
-    switch (cpu->gte_cv) {
-        case 0: cv = cpu->cop2_cr.tr; break;
-        case 1: cv = cpu->cop2_cr.bk; break;
-        case 2: cv = cpu->cop2_cr.fc; break;
-        case 3: {
-            cv.x = 0;
-            cv.y = 0;
-            cv.z = 0;
-        } break;
-    }
-
-    // Bugged case (CV=FC)
-    if (cpu->gte_cv == 2) {
-        R_MAC1 = gte_clamp_mac(cpu, 1, (int64_t)(I64(R_MX12) * I64(R_VY)) + (I64(R_MX13) * I64(R_VZ)));
-        R_MAC2 = gte_clamp_mac(cpu, 2, (int64_t)(I64(R_MX22) * I64(R_VY)) + (I64(R_MX23) * I64(R_VZ)));
-        R_MAC3 = gte_clamp_mac(cpu, 3, (int64_t)(I64(R_MX32) * I64(R_VY)) + (I64(R_MX33) * I64(R_VZ)));
-
-        int64_t mac1 = gte_clamp_mac(cpu, 1, (((int64_t)R_CV1) << 12) + (I64(R_MX11) * I64(R_VX))); 
-        int64_t mac2 = gte_clamp_mac(cpu, 2, (((int64_t)R_CV2) << 12) + (I64(R_MX21) * I64(R_VX))); 
-        int64_t mac3 = gte_clamp_mac(cpu, 3, (((int64_t)R_CV3) << 12) + (I64(R_MX31) * I64(R_VX))); 
-
-        gte_clamp_ir(cpu, 1, mac1, 0);
-        gte_clamp_ir(cpu, 2, mac2, 0);
-        gte_clamp_ir(cpu, 3, mac3, 0);
-    } else {
-        R_MAC1 = gte_clamp_mac(cpu, 1, (((int64_t)R_CV1) << 12) + (I64(R_MX11) * I64(R_VX)) + (I64(R_MX12) * I64(R_VY)) + (I64(R_MX13) * I64(R_VZ)));
-        R_MAC2 = gte_clamp_mac(cpu, 2, (((int64_t)R_CV2) << 12) + (I64(R_MX21) * I64(R_VX)) + (I64(R_MX22) * I64(R_VY)) + (I64(R_MX23) * I64(R_VZ)));
-        R_MAC3 = gte_clamp_mac(cpu, 3, (((int64_t)R_CV3) << 12) + (I64(R_MX31) * I64(R_VX)) + (I64(R_MX32) * I64(R_VY)) + (I64(R_MX33) * I64(R_VZ)));
-    }
-
-    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
-    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
-    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
-}
-
-#undef R_VX
-#undef R_VY
-#undef R_VZ
-#undef R_MX11
-#undef R_MX11
-#undef R_MX12
-#undef R_MX13
-#undef R_MX21
-#undef R_MX22
-#undef R_MX23
-#undef R_MX31
-#undef R_MX32
-#undef R_MX33
-#undef R_CV1
-#undef R_CV2
-#undef R_CV3
-
-// To-do: Fix flags
-void psx_gte_i_ncds(psx_cpu_t* cpu) {
-    R_FLAG = 0;
-
-    int64_t vx = (int64_t)((int16_t)cpu->cop2_dr.v[0].p[0]);
-    int64_t vy = (int64_t)((int16_t)cpu->cop2_dr.v[0].p[1]);
-    int64_t vz = (int64_t)cpu->cop2_dr.v[0].z;
-
-    R_MAC1 = gte_clamp_mac(cpu, 1, (I64(R_L11) * vx) + (I64(R_L12) * vy) + (I64(R_L13) * vz));
-    R_MAC2 = gte_clamp_mac(cpu, 2, (I64(R_L21) * vx) + (I64(R_L22) * vy) + (I64(R_L23) * vz));
-    R_MAC3 = gte_clamp_mac(cpu, 3, (I64(R_L31) * vx) + (I64(R_L32) * vy) + (I64(R_L33) * vz));
-
-    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
-    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
-    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
-
-    R_MAC1 = gte_clamp_mac(cpu, 1, (I64(R_RBK) << 12) + (I64(R_LR1) * I64(R_IR1)) + (I64(R_LR2) * I64(R_IR2)) + (I64(R_LR3) * I64(R_IR3)));
-    R_MAC2 = gte_clamp_mac(cpu, 2, (I64(R_GBK) << 12) + (I64(R_LG1) * I64(R_IR1)) + (I64(R_LG2) * I64(R_IR2)) + (I64(R_LG3) * I64(R_IR3)));
-    R_MAC3 = gte_clamp_mac(cpu, 3, (I64(R_BBK) << 12) + (I64(R_LB1) * I64(R_IR1)) + (I64(R_LB2) * I64(R_IR2)) + (I64(R_LB3) * I64(R_IR3)));
-
-    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
-    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
-    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
-
-    int64_t ir1 = gte_clamp_ir(cpu, 1, gte_clamp_mac(cpu, 1, ((I64(R_RFC) << 12) - ((I64(R_RC << 4)) * I64(R_IR1)))), 0);
-    int64_t ir2 = gte_clamp_ir(cpu, 2, gte_clamp_mac(cpu, 2, ((I64(R_GFC) << 12) - ((I64(R_GC << 4)) * I64(R_IR2)))), 0);
-    int64_t ir3 = gte_clamp_ir(cpu, 3, gte_clamp_mac(cpu, 3, ((I64(R_BFC) << 12) - ((I64(R_BC << 4)) * I64(R_IR3)))), 0);
-
-    R_MAC1 = gte_clamp_mac(cpu, 1, ((I64(R_RC << 4)) * I64(R_IR1)) + (I64(R_IR0) * ir1));
-    R_MAC2 = gte_clamp_mac(cpu, 2, ((I64(R_GC << 4)) * I64(R_IR2)) + (I64(R_IR0) * ir2));
-    R_MAC3 = gte_clamp_mac(cpu, 3, ((I64(R_BC << 4)) * I64(R_IR3)) + (I64(R_IR0) * ir3));
-
-    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
-    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
-    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
-
-    R_RGB0 = R_RGB1;
-    R_RGB1 = R_RGB2;
-    R_CD2 = R_CODE;
-
-    R_RC2 = gte_clamp_rgb(cpu, 1, R_MAC1 >> 4);
-    R_GC2 = gte_clamp_rgb(cpu, 2, R_MAC2 >> 4);
-    R_BC2 = gte_clamp_rgb(cpu, 3, R_MAC3 >> 4);
-}
-
-void psx_gte_i_cdp(psx_cpu_t* cpu) {
-    log_fatal("cdp: Unimplemented GTE instruction");
-}
-
-void psx_gte_i_ncdt(psx_cpu_t* cpu) {
-    log_fatal("ncdt: Unimplemented GTE instruction");
-}
-
-void psx_gte_i_nccs(psx_cpu_t* cpu) {
-    log_fatal("nccs: Unimplemented GTE instruction");
-}
-
-void psx_gte_i_cc(psx_cpu_t* cpu) {
-    log_fatal("cc: Unimplemented GTE instruction");
-}
-
-void psx_gte_i_ncs(psx_cpu_t* cpu) {
-    log_fatal("ncs: Unimplemented GTE instruction");
-}
-
-void psx_gte_i_nct(psx_cpu_t* cpu) {
-    log_fatal("nct: Unimplemented GTE instruction");
-}
-
-void psx_gte_i_sqr(psx_cpu_t* cpu) {
-    R_FLAG = 0;
-
-    R_MAC1 = gte_clamp_mac(cpu, 1, I64(R_IR1) * I64(R_IR1));
-    R_MAC2 = gte_clamp_mac(cpu, 2, I64(R_IR2) * I64(R_IR2));
-    R_MAC3 = gte_clamp_mac(cpu, 3, I64(R_IR3) * I64(R_IR3));
-
-    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
-    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
-    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
-}
-
-void psx_gte_i_dcpl(psx_cpu_t* cpu) {
-    log_fatal("dpcl: Unimplemented GTE instruction");
-}
-
-void psx_gte_i_dpct(psx_cpu_t* cpu) {
-    log_fatal("dpct: Unimplemented GTE instruction");
-}
-
-void psx_gte_i_avsz3(psx_cpu_t* cpu) {
-    R_FLAG = 0;
-
-    int64_t avg = I64(R_ZSF3) * (R_SZ1 + R_SZ2 + R_SZ3);
-
-    R_MAC0 = (int)gte_clamp_mac0(cpu, avg);
-    R_OTZ = gte_clamp_sz3(cpu, avg >> 12);
-}
-
-void psx_gte_i_avsz4(psx_cpu_t* cpu) {
-    R_FLAG = 0;
-
-    int64_t avg = I64(R_ZSF4) * (R_SZ0 + R_SZ1 + R_SZ2 + R_SZ3);
-
-    R_MAC0 = (int)gte_clamp_mac0(cpu, avg);
-    R_OTZ = gte_clamp_sz3(cpu, avg >> 12);
-}
-
-void psx_gte_i_rtpt(psx_cpu_t* cpu) {
-    GTE_RTP(0);
-    GTE_RTP(1);
-    GTE_RTP_DQ(2);
-}
-
-void psx_gte_i_gpf(psx_cpu_t* cpu) {
-    R_FLAG = 0;
-
-    R_MAC1 = gte_clamp_mac(cpu, 1, R_IR0 * R_IR1);
-    R_MAC2 = gte_clamp_mac(cpu, 2, R_IR0 * R_IR2);
-    R_MAC3 = gte_clamp_mac(cpu, 3, R_IR0 * R_IR3);
-    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
-    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
-    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
-    R_RGB0 = R_RGB1;
-    R_RGB1 = R_RGB2;
-    R_CD2 = R_CODE;
-    R_RC2 = gte_clamp_rgb(cpu, 1, R_MAC1 >> 4);
-    R_GC2 = gte_clamp_rgb(cpu, 2, R_MAC2 >> 4);
-    R_BC2 = gte_clamp_rgb(cpu, 3, R_MAC3 >> 4);
-}
-
-void psx_gte_i_gpl(psx_cpu_t* cpu) {
-    R_FLAG = 0;
-
-    R_MAC1 = gte_clamp_mac(cpu, 1, (I64(R_MAC1) << cpu->gte_sf) + (R_IR0 * R_IR1));
-    R_MAC2 = gte_clamp_mac(cpu, 2, (I64(R_MAC2) << cpu->gte_sf) + (R_IR0 * R_IR2));
-    R_MAC3 = gte_clamp_mac(cpu, 3, (I64(R_MAC3) << cpu->gte_sf) + (R_IR0 * R_IR3));
-    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
-    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
-    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
-    R_RGB0 = R_RGB1;
-    R_RGB1 = R_RGB2;
-    R_CD2 = R_CODE;
-    R_RC2 = gte_clamp_rgb(cpu, 1, R_MAC1 >> 4);
-    R_GC2 = gte_clamp_rgb(cpu, 2, R_MAC2 >> 4);
-    R_BC2 = gte_clamp_rgb(cpu, 3, R_MAC3 >> 4);
-}
-
-void psx_gte_i_ncct(psx_cpu_t* cpu) {
-    log_fatal("ncct: Unimplemented GTE instruction");
-}
-
-#undef R_R0
-#undef R_A0
-#undef R_RA
-
-#undef OP
-#undef S
-#undef T
-#undef D
-#undef IMM5
-#undef CMT
-#undef SOP
-#undef IMM26
-#undef IMM16
-#undef IMM16S
-
-#undef TRACE_M
-#undef TRACE_I16S
-#undef TRACE_I16D
-#undef TRACE_I5D
-#undef TRACE_I26
-#undef TRACE_RT
-#undef TRACE_C0M
-#undef TRACE_C2M
-#undef TRACE_C2MC
-#undef TRACE_B
-#undef TRACE_RS
-#undef TRACE_MTF
-#undef TRACE_RD
-#undef TRACE_MD
-#undef TRACE_I20
-#undef TRACE_N
-
-#undef DO_PENDING_LOAD
-
-#undef DEBUG_ALL
-
-#undef SE8
+#include "cpu.h"
+#include "bus.h"
+#include "log.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "cpu_debug.h"
+
+static const psx_cpu_instruction_t g_psx_cpu_secondary_table[] = {
+    psx_cpu_i_sll    , psx_cpu_i_invalid, psx_cpu_i_srl    , psx_cpu_i_sra    ,
+    psx_cpu_i_sllv   , psx_cpu_i_invalid, psx_cpu_i_srlv   , psx_cpu_i_srav   ,
+    psx_cpu_i_jr     , psx_cpu_i_jalr   , psx_cpu_i_invalid, psx_cpu_i_invalid,
+    psx_cpu_i_syscall, psx_cpu_i_break  , psx_cpu_i_invalid, psx_cpu_i_invalid,
+    psx_cpu_i_mfhi   , psx_cpu_i_mthi   , psx_cpu_i_mflo   , psx_cpu_i_mtlo   ,
+    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
+    psx_cpu_i_mult   , psx_cpu_i_multu  , psx_cpu_i_div    , psx_cpu_i_divu   ,
+    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
+    psx_cpu_i_add    , psx_cpu_i_addu   , psx_cpu_i_sub    , psx_cpu_i_subu   ,
+    psx_cpu_i_and    , psx_cpu_i_or     , psx_cpu_i_xor    , psx_cpu_i_nor    ,
+    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_slt    , psx_cpu_i_sltu   ,
+    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
+    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
+    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
+    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
+    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid
+};
+
+static const psx_cpu_instruction_t g_psx_cpu_primary_table[] = {
+    psx_cpu_i_special, psx_cpu_i_bxx    , psx_cpu_i_j      , psx_cpu_i_jal    ,
+    psx_cpu_i_beq    , psx_cpu_i_bne    , psx_cpu_i_blez   , psx_cpu_i_bgtz   ,
+    psx_cpu_i_addi   , psx_cpu_i_addiu  , psx_cpu_i_slti   , psx_cpu_i_sltiu  ,
+    psx_cpu_i_andi   , psx_cpu_i_ori    , psx_cpu_i_xori   , psx_cpu_i_lui    ,
+    psx_cpu_i_cop0   , psx_cpu_i_cop1   , psx_cpu_i_cop2   , psx_cpu_i_cop3   ,
+    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
+    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
+    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
+    psx_cpu_i_lb     , psx_cpu_i_lh     , psx_cpu_i_lwl    , psx_cpu_i_lw     ,
+    psx_cpu_i_lbu    , psx_cpu_i_lhu    , psx_cpu_i_lwr    , psx_cpu_i_invalid,
+    psx_cpu_i_sb     , psx_cpu_i_sh     , psx_cpu_i_swl    , psx_cpu_i_sw     ,
+    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_swr    , psx_cpu_i_invalid,
+    psx_cpu_i_lwc0   , psx_cpu_i_lwc1   , psx_cpu_i_lwc2   , psx_cpu_i_lwc3   ,
+    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
+    psx_cpu_i_swc0   , psx_cpu_i_swc1   , psx_cpu_i_swc2   , psx_cpu_i_swc3   ,
+    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid
+};
+
+static const psx_cpu_instruction_t g_psx_cpu_cop0_table[] = {
+    psx_cpu_i_mfc0   , psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
+    psx_cpu_i_mtc0   , psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
+    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
+    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
+    psx_cpu_i_rfe    , psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
+    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
+    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
+    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid
+};
+
+static const psx_cpu_instruction_t g_psx_cpu_cop2_table[] = {
+    psx_cpu_i_mfc2   , psx_cpu_i_invalid, psx_cpu_i_cfc2   , psx_cpu_i_invalid,
+    psx_cpu_i_mtc2   , psx_cpu_i_invalid, psx_cpu_i_ctc2   , psx_cpu_i_invalid,
+    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
+    psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid, psx_cpu_i_invalid,
+    psx_cpu_i_gte    , psx_cpu_i_gte    , psx_cpu_i_gte    , psx_cpu_i_gte    ,
+    psx_cpu_i_gte    , psx_cpu_i_gte    , psx_cpu_i_gte    , psx_cpu_i_gte    ,
+    psx_cpu_i_gte    , psx_cpu_i_gte    , psx_cpu_i_gte    , psx_cpu_i_gte    ,
+    psx_cpu_i_gte    , psx_cpu_i_gte    , psx_cpu_i_gte    , psx_cpu_i_gte
+};
+
+static const psx_cpu_instruction_t g_psx_cpu_bxx_table[] = {
+    psx_cpu_i_bltz   , psx_cpu_i_bgez   , psx_cpu_i_bltz   , psx_cpu_i_bgez   ,
+    psx_cpu_i_bltz   , psx_cpu_i_bgez   , psx_cpu_i_bltz   , psx_cpu_i_bgez   ,
+    psx_cpu_i_bltz   , psx_cpu_i_bgez   , psx_cpu_i_bltz   , psx_cpu_i_bgez   ,
+    psx_cpu_i_bltz   , psx_cpu_i_bgez   , psx_cpu_i_bltz   , psx_cpu_i_bgez   ,
+    psx_cpu_i_bltzal , psx_cpu_i_bgezal , psx_cpu_i_bltz   , psx_cpu_i_bgez   ,
+    psx_cpu_i_bltz   , psx_cpu_i_bgez   , psx_cpu_i_bltz   , psx_cpu_i_bgez   ,
+    psx_cpu_i_bltz   , psx_cpu_i_bgez   , psx_cpu_i_bltz   , psx_cpu_i_bgez   ,
+    psx_cpu_i_bltz   , psx_cpu_i_bgez   , psx_cpu_i_bltz   , psx_cpu_i_bgez
+};
+
+static const psx_cpu_instruction_t g_psx_gte_table[] = {
+    psx_gte_i_invalid, psx_gte_i_rtps   , psx_gte_i_invalid, psx_gte_i_invalid,
+    psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_nclip  , psx_gte_i_invalid,
+    psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid,
+    psx_gte_i_op     , psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid,
+    psx_gte_i_dpcs   , psx_gte_i_intpl  , psx_gte_i_mvmva  , psx_gte_i_ncds   ,
+    psx_gte_i_cdp    , psx_gte_i_invalid, psx_gte_i_ncdt   , psx_gte_i_invalid,
+    psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_nccs   ,
+    psx_gte_i_cc     , psx_gte_i_invalid, psx_gte_i_ncs    , psx_gte_i_invalid,
+    psx_gte_i_nct    , psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid,
+    psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid,
+    psx_gte_i_sqr    , psx_gte_i_dcpl   , psx_gte_i_dpct   , psx_gte_i_invalid,
+    psx_gte_i_invalid, psx_gte_i_avsz3  , psx_gte_i_avsz4  , psx_gte_i_invalid,
+    psx_gte_i_rtpt   , psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid,
+    psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid,
+    psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid, psx_gte_i_invalid,
+    psx_gte_i_invalid, psx_gte_i_gpf    , psx_gte_i_gpl    , psx_gte_i_ncct
+};
+
+static const uint32_t g_psx_cpu_cop0_write_mask_table[] = {
+    0x00000000, // cop0r0   - N/A
+    0x00000000, // cop0r1   - N/A
+    0x00000000, // cop0r2   - N/A
+    0xffffffff, // BPC      - Breakpoint on execute (R/W)
+    0x00000000, // cop0r4   - N/A
+    0xffffffff, // BDA      - Breakpoint on data access (R/W)
+    0x00000000, // JUMPDEST - Randomly memorized jump address (R)
+    0xffc0f03f, // DCIC     - Breakpoint control (R/W)
+    0x00000000, // BadVaddr - Bad Virtual Address (R)
+    0xffffffff, // BDAM     - Data Access breakpoint mask (R/W)
+    0x00000000, // cop0r10  - N/A
+    0xffffffff, // BPCM     - Execute breakpoint mask (R/W)
+    0xffffffff, // SR       - System status register (R/W)
+    0x00000300, // CAUSE    - Describes the most recently recognised exception (R)
+    0x00000000, // EPC      - Return Address from Trap (R)
+    0x00000000  // PRID     - Processor ID (R)
+};
+
+static const uint8_t g_psx_gte_unr_table[] = {
+    0xff, 0xfd, 0xfb, 0xf9, 0xf7, 0xf5, 0xf3, 0xf1,
+    0xef, 0xee, 0xec, 0xea, 0xe8, 0xe6, 0xe4, 0xe3,
+    0xe1, 0xdf, 0xdd, 0xdc, 0xda, 0xd8, 0xd6, 0xd5,
+    0xd3, 0xd1, 0xd0, 0xce, 0xcd, 0xcb, 0xc9, 0xc8,
+    0xc6, 0xc5, 0xc3, 0xc1, 0xc0, 0xbe, 0xbd, 0xbb,
+    0xba, 0xb8, 0xb7, 0xb5, 0xb4, 0xb2, 0xb1, 0xb0,
+    0xae, 0xad, 0xab, 0xaa, 0xa9, 0xa7, 0xa6, 0xa4,
+    0xa3, 0xa2, 0xa0, 0x9f, 0x9e, 0x9c, 0x9b, 0x9a,
+    0x99, 0x97, 0x96, 0x95, 0x94, 0x92, 0x91, 0x90,
+    0x8f, 0x8d, 0x8c, 0x8b, 0x8a, 0x89, 0x87, 0x86,
+    0x85, 0x84, 0x83, 0x82, 0x81, 0x7f, 0x7e, 0x7d,
+    0x7c, 0x7b, 0x7a, 0x79, 0x78, 0x77, 0x75, 0x74,
+    0x73, 0x72, 0x71, 0x70, 0x6f, 0x6e, 0x6d, 0x6c,
+    0x6b, 0x6a, 0x69, 0x68, 0x67, 0x66, 0x65, 0x64,
+    0x63, 0x62, 0x61, 0x60, 0x5f, 0x5e, 0x5d, 0x5d,
+    0x5c, 0x5b, 0x5a, 0x59, 0x58, 0x57, 0x56, 0x55,
+    0x54, 0x53, 0x53, 0x52, 0x51, 0x50, 0x4f, 0x4e,
+    0x4d, 0x4d, 0x4c, 0x4b, 0x4a, 0x49, 0x48, 0x48,
+    0x47, 0x46, 0x45, 0x44, 0x43, 0x43, 0x42, 0x41,
+    0x40, 0x3f, 0x3f, 0x3e, 0x3d, 0x3c, 0x3c, 0x3b,
+    0x3a, 0x39, 0x39, 0x38, 0x37, 0x36, 0x36, 0x35,
+    0x34, 0x33, 0x33, 0x32, 0x31, 0x31, 0x30, 0x2f,
+    0x2e, 0x2e, 0x2d, 0x2c, 0x2c, 0x2b, 0x2a, 0x2a,
+    0x29, 0x28, 0x28, 0x27, 0x26, 0x26, 0x25, 0x24,
+    0x24, 0x23, 0x22, 0x22, 0x21, 0x20, 0x20, 0x1f,
+    0x1e, 0x1e, 0x1d, 0x1d, 0x1c, 0x1b, 0x1b, 0x1a,
+    0x19, 0x19, 0x18, 0x18, 0x17, 0x16, 0x16, 0x15,
+    0x15, 0x14, 0x14, 0x13, 0x12, 0x12, 0x11, 0x11,
+    0x10, 0x0f, 0x0f, 0x0e, 0x0e, 0x0d, 0x0d, 0x0c,
+    0x0c, 0x0b, 0x0a, 0x0a, 0x09, 0x09, 0x08, 0x08,
+    0x07, 0x07, 0x06, 0x06, 0x05, 0x05, 0x04, 0x04,
+    0x03, 0x03, 0x02, 0x02, 0x01, 0x01, 0x00, 0x00,
+    0x00
+};
+
+#define OP ((cpu->opcode >> 26) & 0x3f)
+#define S ((cpu->opcode >> 21) & 0x1f)
+#define T ((cpu->opcode >> 16) & 0x1f)
+#define D ((cpu->opcode >> 11) & 0x1f)
+#define IMM5 ((cpu->opcode >> 6) & 0x1f)
+#define CMT ((cpu->opcode >> 6) & 0xfffff)
+#define SOP (cpu->opcode & 0x3f)
+#define IMM26 (cpu->opcode & 0x3ffffff)
+#define IMM16 (cpu->opcode & 0xffff)
+#define IMM16S ((int32_t)((int16_t)IMM16))
+
+#define COP2_DR(idx) ((uint32_t*)(&cpu->cop2_dr))[idx]
+#define COP2_CR(idx) ((uint32_t*)(&cpu->cop2_cr))[idx]
+
+#define R_R0 (cpu->r[0])
+#define R_A0 (cpu->r[4])
+#define R_RA (cpu->r[31])
+
+#define DO_PENDING_LOAD \
+    cpu->r[cpu->load_d] = cpu->load_v; \
+    R_R0 = 0; \
+    cpu->load_v = 0xffffffff; \
+    cpu->load_d = 0;
+
+#define SE8(v) ((int32_t)((int8_t)v))
+#define SE16(v) ((int32_t)((int16_t)v))
+
+#define BRANCH(offset) { \
+    cpu->next_pc = cpu->next_pc + (offset); \
+    cpu->next_pc = cpu->next_pc - 4; \
+    cpu->branch = 1; \
+    cpu->branch_taken = 1; }
+
+void cpu_a_kcall_hook(psx_cpu_t* cpu) {
+    switch (cpu->r[9]) {
+        case 0x09: putc(R_A0, stdout); break;
+        case 0x3c: putchar(R_A0); break;
+        case 0x3e: {
+            uint32_t src = R_A0;
+
+            char c = psx_bus_read8(cpu->bus, src++);
+
+            while (c) {
+                putchar(c);
+
+                c = psx_bus_read8(cpu->bus, src++);
+            }
+        } break;
+    }
+}
+
+void cpu_b_kcall_hook(psx_cpu_t* cpu) {
+    switch (cpu->r[9]) {
+        case 0x3b: putc(R_A0, stdout); break;
+        case 0x3d: putchar(R_A0); break;
+        case 0x3f: {
+            uint32_t src = R_A0;
+
+            char c = psx_bus_read8(cpu->bus, src++);
+
+            while (c) {
+                putchar(c);
+
+                c = psx_bus_read8(cpu->bus, src++);
+            }
+        } break;
+    }
+}
+
+psx_cpu_t* psx_cpu_create() {
+    return (psx_cpu_t*)malloc(sizeof(psx_cpu_t));
+}
+
+void cpu_a_kcall_hook(psx_cpu_t*);
+void cpu_b_kcall_hook(psx_cpu_t*);
+
+void psx_cpu_fetch(psx_cpu_t* cpu) {
+    //cpu->buf[0] = psx_bus_read32(cpu->bus, cpu->pc);
+    //cpu->pc += 4;
+
+    // Discard fetch cycles
+    psx_bus_get_access_cycles(cpu->bus);
+}
+
+void psx_cpu_destroy(psx_cpu_t* cpu) {
+    free(cpu);
+}
+
+void psx_cpu_set_a_kcall_hook(psx_cpu_t* cpu, psx_cpu_kcall_hook_t hook) {
+    cpu->a_function_hook = hook;
+}
+
+void psx_cpu_set_b_kcall_hook(psx_cpu_t* cpu, psx_cpu_kcall_hook_t hook) {
+    cpu->b_function_hook = hook;
+}
+
+void psx_cpu_save_state(psx_cpu_t* cpu, FILE* file) {
+    fwrite((char*)cpu, sizeof(*cpu) - sizeof(psx_bus_t*), 1, file);
+}
+
+void psx_cpu_load_state(psx_cpu_t* cpu, FILE* file) {
+    if (!fread((char*)cpu, sizeof(*cpu) - sizeof(psx_bus_t*), 1, file)) {
+        perror("Error reading CPU state");
+
+        exit(1);
+    }
+}
+
+void psx_cpu_init(psx_cpu_t* cpu, psx_bus_t* bus) {
+    memset(cpu, 0, sizeof(psx_cpu_t));
+
+    psx_cpu_set_a_kcall_hook(cpu, cpu_a_kcall_hook);
+    psx_cpu_set_b_kcall_hook(cpu, cpu_b_kcall_hook);
+
+    cpu->bus = bus;
+    cpu->pc = 0xbfc00000;
+    cpu->next_pc = cpu->pc + 4;
+
+    cpu->cop0_r[COP0_SR] = 0x10900000;
+    cpu->cop0_r[COP0_PRID] = 0x00000002;
+}
+
+void psx_cpu_cycle(psx_cpu_t* cpu) {
+    cpu->last_cycles = 0;
+
+    if ((cpu->pc & 0x3fffffff) == 0x000000b4)
+        if (cpu->b_function_hook)
+            cpu->b_function_hook(cpu);
+
+    cpu->saved_pc = cpu->pc;
+    cpu->delay_slot = cpu->branch;
+    cpu->branch = 0;
+    cpu->branch_taken = 0;
+
+    if (cpu->saved_pc & 3)
+        psx_cpu_exception(cpu, CAUSE_ADEL);
+
+    cpu->opcode = psx_bus_read32(cpu->bus, cpu->pc);
+
+    cpu->pc = cpu->next_pc;
+    cpu->next_pc += 4;
+
+    if (psx_cpu_check_irq(cpu)) {
+        psx_cpu_exception(cpu, CAUSE_INT);
+
+        return;
+    }
+
+    g_psx_cpu_primary_table[OP](cpu);
+
+    // Not even trying to get precise timings here
+    cpu->last_cycles = 2;
+    cpu->total_cycles += cpu->last_cycles;
+
+    cpu->r[0] = 0;
+}
+
+int psx_cpu_check_irq(psx_cpu_t* cpu) {
+    return (cpu->cop0_r[COP0_SR] & SR_IEC) &&
+           (cpu->cop0_r[COP0_SR] & cpu->cop0_r[COP0_CAUSE] & 0x00000700);
+}
+
+void psx_cpu_exception(psx_cpu_t* cpu, uint32_t cause) {
+    // Set excode and clear 3 LSBs
+    cpu->cop0_r[COP0_CAUSE] &= 0xffffff80;
+    cpu->cop0_r[COP0_CAUSE] |= cause;
+
+    cpu->cop0_r[COP0_EPC] = cpu->saved_pc;
+
+    if (cpu->delay_slot) {
+        cpu->cop0_r[COP0_EPC] -= 4;
+        cpu->cop0_r[COP0_CAUSE] |= 0x80000000;
+    }
+
+    if ((cause == CAUSE_INT) && ((cpu->opcode & 0xfe000000) == 0x4a000000))
+        cpu->cop0_r[COP0_EPC] += 4;
+
+    // Do exception stack push
+    uint32_t mode = cpu->cop0_r[COP0_SR] & 0x3f;
+
+    cpu->cop0_r[COP0_SR] &= 0xffffffc0;
+    cpu->cop0_r[COP0_SR] |= (mode << 2) & 0x3f;
+
+    // Set PC to the vector selected on BEV
+    cpu->pc = (cpu->cop0_r[COP0_SR] & SR_BEV) ? 0xbfc00180 : 0x80000080;
+
+    cpu->next_pc = cpu->pc + 4;
+}
+
+void psx_cpu_set_irq_pending(psx_cpu_t* cpu) {
+    cpu->cop0_r[COP0_CAUSE] |= SR_IM2;
+}
+
+void psx_cpu_i_invalid(psx_cpu_t* cpu) {
+    log_fatal("%08x: Illegal instruction %08x", cpu->pc - 8, cpu->opcode);
+
+    psx_cpu_exception(cpu, CAUSE_RI);
+}
+
+// Primary
+void psx_cpu_i_special(psx_cpu_t* cpu) {
+    g_psx_cpu_secondary_table[SOP](cpu);
+}
+
+void psx_cpu_i_bxx(psx_cpu_t* cpu) {
+    cpu->branch = 1;
+    cpu->branch_taken = 0;
+
+    g_psx_cpu_bxx_table[T](cpu);
+}
+
+// BXX
+void psx_cpu_i_bltz(psx_cpu_t* cpu) {
+    TRACE_B("bltz");
+
+    int32_t s = (int32_t)cpu->r[S];
+
+    DO_PENDING_LOAD;
+
+    if ((int32_t)s < (int32_t)0)
+        BRANCH(IMM16S << 2);
+}
+
+void psx_cpu_i_bgez(psx_cpu_t* cpu) {
+    TRACE_B("bgez");
+
+    int32_t s = (int32_t)cpu->r[S];
+
+    DO_PENDING_LOAD;
+
+    if ((int32_t)s >= (int32_t)0)
+        BRANCH(IMM16S << 2);
+}
+
+void psx_cpu_i_bltzal(psx_cpu_t* cpu) {
+    TRACE_B("bltzal");
+
+    int32_t s = (int32_t)cpu->r[S];
+
+    DO_PENDING_LOAD;
+
+    R_RA = cpu->next_pc;
+
+    if ((int32_t)s < (int32_t)0)
+        BRANCH(IMM16S << 2);
+}
+
+void psx_cpu_i_bgezal(psx_cpu_t* cpu) {
+    TRACE_B("bgezal");
+
+    int32_t s = (int32_t)cpu->r[S];
+
+    DO_PENDING_LOAD;
+
+    R_RA = cpu->next_pc;
+
+    if ((int32_t)s >= (int32_t)0)
+        BRANCH(IMM16S << 2);
+}
+
+void psx_cpu_i_j(psx_cpu_t* cpu) {
+    cpu->branch = 1;
+
+    TRACE_I26("j");
+
+    DO_PENDING_LOAD;
+
+    cpu->next_pc = (cpu->next_pc & 0xf0000000) | (IMM26 << 2);
+}
+
+void psx_cpu_i_jal(psx_cpu_t* cpu) {
+    cpu->branch = 1;
+
+    TRACE_I26("jal");
+
+    DO_PENDING_LOAD;
+
+    R_RA = cpu->next_pc;
+
+    cpu->next_pc = (cpu->next_pc & 0xf0000000) | (IMM26 << 2);
+}
+
+void psx_cpu_i_beq(psx_cpu_t* cpu) {
+    cpu->branch = 1;
+    cpu->branch_taken = 0;
+
+    TRACE_B("beq");
+
+    uint32_t s = cpu->r[S];
+    uint32_t t = cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    if (s == t)
+        BRANCH(IMM16S << 2);
+}
+
+void psx_cpu_i_bne(psx_cpu_t* cpu) {
+    cpu->branch = 1;
+    cpu->branch_taken = 0;
+
+    TRACE_B("bne");
+
+    uint32_t s = cpu->r[S];
+    uint32_t t = cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    if (s != t)
+        BRANCH(IMM16S << 2);
+}
+
+void psx_cpu_i_blez(psx_cpu_t* cpu) {
+    cpu->branch = 1;
+    cpu->branch_taken = 0;
+
+    TRACE_B("blez");
+
+    int32_t s = (int32_t)cpu->r[S];
+
+    DO_PENDING_LOAD;
+
+    if ((int32_t)s <= (int32_t)0)
+        BRANCH(IMM16S << 2);
+}
+
+void psx_cpu_i_bgtz(psx_cpu_t* cpu) {
+    cpu->branch = 1;
+    cpu->branch_taken = 0;
+
+    TRACE_B("bgtz");
+
+    int32_t s = (int32_t)cpu->r[S];
+
+    DO_PENDING_LOAD;
+
+    if ((int32_t)s > (int32_t)0)
+        BRANCH(IMM16S << 2);
+}
+
+void psx_cpu_i_addi(psx_cpu_t* cpu) {
+    TRACE_I16D("addi");
+
+    uint32_t s = cpu->r[S];
+
+    DO_PENDING_LOAD;
+
+    uint32_t i = IMM16S;
+    uint32_t r = s + i;
+    uint32_t o = (s ^ r) & (i ^ r);
+
+    if (o & 0x80000000) {
+        psx_cpu_exception(cpu, CAUSE_OV);
+    } else {
+        cpu->r[T] = r;
+    }
+}
+
+void psx_cpu_i_addiu(psx_cpu_t* cpu) {
+    TRACE_I16D("addiu");
+
+    uint32_t s = cpu->r[S];
+
+    DO_PENDING_LOAD;
+
+    cpu->r[T] = s + IMM16S;
+}
+
+void psx_cpu_i_slti(psx_cpu_t* cpu) {
+    TRACE_I16D("slti");
+
+    int32_t s = (int32_t)cpu->r[S];
+
+    DO_PENDING_LOAD;
+
+    cpu->r[T] = s < IMM16S;
+}
+
+void psx_cpu_i_sltiu(psx_cpu_t* cpu) {
+    TRACE_I16D("sltiu");
+
+    uint32_t s = cpu->r[S];
+
+    DO_PENDING_LOAD;
+
+    cpu->r[T] = s < IMM16S;
+}
+
+void psx_cpu_i_andi(psx_cpu_t* cpu) {
+    TRACE_I16D("andi");
+
+    uint32_t s = cpu->r[S];
+
+    DO_PENDING_LOAD;
+
+    cpu->r[T] = s & IMM16;
+}
+
+void psx_cpu_i_ori(psx_cpu_t* cpu) {
+    TRACE_I16D("ori");
+
+    uint32_t s = cpu->r[S];
+
+    DO_PENDING_LOAD;
+
+    cpu->r[T] = s | IMM16;
+}
+
+void psx_cpu_i_xori(psx_cpu_t* cpu) {
+    TRACE_I16D("xori");
+
+    uint32_t s = cpu->r[S];
+
+    DO_PENDING_LOAD;
+
+    cpu->r[T] = s ^ IMM16;
+}
+
+void psx_cpu_i_lui(psx_cpu_t* cpu) {
+    TRACE_I16S("lui");
+
+    DO_PENDING_LOAD;
+
+    cpu->r[T] = IMM16 << 16;
+}
+
+void psx_cpu_i_cop0(psx_cpu_t* cpu) {
+    g_psx_cpu_cop0_table[S](cpu);
+}
+
+void psx_cpu_i_cop1(psx_cpu_t* cpu) {
+    DO_PENDING_LOAD;
+
+    psx_cpu_exception(cpu, CAUSE_CPU);
+}
+
+void psx_cpu_i_cop2(psx_cpu_t* cpu) {
+    g_psx_cpu_cop2_table[S](cpu);
+}
+
+void psx_cpu_i_cop3(psx_cpu_t* cpu) {
+    DO_PENDING_LOAD;
+
+    psx_cpu_exception(cpu, CAUSE_CPU);
+}
+
+void psx_cpu_i_lb(psx_cpu_t* cpu) {
+    TRACE_M("lb");
+
+    uint32_t s = cpu->r[S];
+
+    DO_PENDING_LOAD;
+
+    cpu->load_d = T;
+    cpu->load_v = SE8(psx_bus_read8(cpu->bus, s + IMM16S));
+}
+
+void psx_cpu_i_lh(psx_cpu_t* cpu) {
+    TRACE_M("lh");
+
+    uint32_t s = cpu->r[S];
+
+    DO_PENDING_LOAD;
+
+    uint32_t addr = s + IMM16S;
+
+    if (addr & 0x1) {
+        psx_cpu_exception(cpu, CAUSE_ADEL);
+    } else {
+        cpu->load_d = T;
+        cpu->load_v = SE16(psx_bus_read16(cpu->bus, addr));
+    }
+}
+
+void psx_cpu_i_lwl(psx_cpu_t* cpu) {
+    TRACE_M("lwl");
+
+    uint32_t rt = T;
+    uint32_t s = cpu->r[S];
+    uint32_t t = cpu->r[rt];
+
+    DO_PENDING_LOAD;
+
+    uint32_t addr = s + IMM16S;
+    uint32_t aligned_addr = addr & 0xFFFFFFFC;
+    uint32_t aligned_load = r3000_read32(cpu, aligned_addr);
+
+    if (rt == cpu->load_d)
+        t = cpu->load_v;
+
+    int shift = (int)((addr & 0x3) << 3);
+    uint32_t mask = (uint32_t)0x00FFFFFF >> shift;
+    uint32_t value = (t & mask) | (aligned_load << (24 - shift)); 
+
+    cpu->load_d = rt;
+    cpu->load_v = value;
+
+    // uint32_t addr = cpu->r[S] + IMM16S;
+
+    // uint32_t aligned = psx_bus_read32(cpu->bus, addr & ~0x3);
+
+    // printf("aligned=%08x load_v=%08x addr=%08x prev=%08x\n",
+    //     aligned,
+    //     cpu->load_v,
+    //     addr,
+    //     psx_bus_read32(cpu->bus, addr + 1)
+    // );
+
+    // switch (addr & 0x3) {
+    //     case 3: cpu->load_v = (cpu->load_v & 0x00ffffff) | (aligned << 24); break;
+    //     case 2: cpu->load_v = (cpu->load_v & 0x0000ffff) | (aligned << 16); break;
+    //     case 1: cpu->load_v = (cpu->load_v & 0x000000ff) | (aligned << 8 ); break;
+    //     case 0: cpu->load_v =                               aligned       ; break;
+    // }
+
+    // cpu->load_d = T;
+}
+
+void psx_cpu_i_lw(psx_cpu_t* cpu) {
+    TRACE_M("lw");
+
+    uint32_t s = cpu->r[S];
+    uint32_t addr = s + IMM16S;
+
+    DO_PENDING_LOAD;
+
+    if (addr & 0x3) {
+        psx_cpu_exception(cpu, CAUSE_ADEL);
+    } else {
+        cpu->load_d = T;
+        cpu->load_v = psx_bus_read32(cpu->bus, addr);
+    }
+}
+
+void psx_cpu_i_lbu(psx_cpu_t* cpu) {
+    TRACE_M("lbu");
+
+    uint32_t s = cpu->r[S];
+
+    DO_PENDING_LOAD;
+
+    cpu->load_d = T;
+    cpu->load_v = psx_bus_read8(cpu->bus, s + IMM16S);
+}
+
+void psx_cpu_i_lhu(psx_cpu_t* cpu) {
+    TRACE_M("lhu");
+
+    uint32_t s = cpu->r[S];
+    uint32_t addr = s + IMM16S;
+
+    DO_PENDING_LOAD;
+
+    if (addr & 0x1) {
+        psx_cpu_exception(cpu, CAUSE_ADEL);
+    } else {
+        cpu->load_d = T;
+        cpu->load_v = psx_bus_read16(cpu->bus, addr);
+    }
+}
+
+void psx_cpu_i_lwr(psx_cpu_t* cpu) {
+    TRACE_M("lwr");
+
+    uint32_t rt = T;
+    uint32_t s = cpu->r[S];
+    uint32_t t = cpu->r[rt];
+
+    DO_PENDING_LOAD;
+
+    uint32_t addr = s + IMM16S;
+    uint32_t aligned_addr = addr & 0xFFFFFFFC;
+    uint32_t aligned_load = r3000_read32(cpu, aligned_addr);
+
+    if (rt == cpu->load_d)
+        t = cpu->load_v;
+
+    int shift = (int)((addr & 0x3) << 3);
+    uint32_t mask = 0xFFFFFF00 << (24 - shift);
+    uint32_t value = (t & mask) | (aligned_load >> shift); 
+
+    cpu->load_d = rt;
+    cpu->load_v = value;
+
+    // uint32_t addr = cpu->r[S] + IMM16S;
+
+    // uint32_t aligned = psx_bus_read32(cpu->bus, addr & ~0x3);
+
+    // switch (addr & 0x3) {
+    //     case 0: cpu->load_v =                               aligned       ; break;
+    //     case 1: cpu->load_v = (cpu->load_v & 0xff000000) | (aligned >> 8 ); break;
+    //     case 2: cpu->load_v = (cpu->load_v & 0xffff0000) | (aligned >> 16); break;
+    //     case 3: cpu->load_v = (cpu->load_v & 0xffffff00) | (aligned >> 24); break;
+    // }
+
+    // cpu->load_d = T;
+}
+
+void psx_cpu_i_sb(psx_cpu_t* cpu) {
+    TRACE_M("sb");
+
+    uint32_t s = cpu->r[S];
+    uint32_t t = cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    // Cache isolated
+    if (cpu->cop0_r[COP0_SR] & SR_ISC) {
+        log_debug("Ignoring write while cache is isolated");
+
+        return;
+    }
+
+    psx_bus_write8(cpu->bus, s + IMM16S, t);
+}
+
+void psx_cpu_i_sh(psx_cpu_t* cpu) {
+    TRACE_M("sh");
+
+    uint32_t s = cpu->r[S];
+    uint32_t t = cpu->r[T];
+    uint32_t addr = s + IMM16S;
+
+    DO_PENDING_LOAD;
+
+    // Cache isolated
+    if (cpu->cop0_r[COP0_SR] & SR_ISC) {
+        log_debug("Ignoring write while cache is isolated");
+
+        return;
+    }
+
+    if (addr & 0x1) {
+        psx_cpu_exception(cpu, CAUSE_ADES);
+    } else {
+        psx_bus_write16(cpu->bus, addr, t);
+    }
+}
+
+void psx_cpu_i_swl(psx_cpu_t* cpu) {
+    TRACE_M("swl");
+
+    uint32_t s = cpu->r[S];
+
+    DO_PENDING_LOAD;
+
+    uint32_t addr = s + IMM16S;
+    uint32_t aligned = addr & ~0x3;
+    uint32_t v = psx_bus_read32(cpu->bus, aligned);
+
+    switch (addr & 0x3) {
+        case 0: v = (v & 0xffffff00) | (cpu->r[T] >> 24); break;
+        case 1: v = (v & 0xffff0000) | (cpu->r[T] >> 16); break;
+        case 2: v = (v & 0xff000000) | (cpu->r[T] >> 8 ); break;
+        case 3: v =                     cpu->r[T]       ; break;
+    }
+
+    psx_bus_write32(cpu->bus, aligned, v);
+}
+
+void psx_cpu_i_sw(psx_cpu_t* cpu) {
+    TRACE_M("sw");
+
+    uint32_t s = cpu->r[S];
+    uint32_t t = cpu->r[T];
+    uint32_t addr = s + IMM16S;
+
+    DO_PENDING_LOAD;
+
+    // Cache isolated
+    if (cpu->cop0_r[COP0_SR] & SR_ISC) {
+        log_debug("Ignoring write while cache is isolated");
+
+        return;
+    }
+
+    if (addr & 0x3) {
+        psx_cpu_exception(cpu, CAUSE_ADES);
+    } else {
+        psx_bus_write32(cpu->bus, addr, t);
+    }
+}
+
+void psx_cpu_i_swr(psx_cpu_t* cpu) {
+    TRACE_M("swr");
+
+    uint32_t s = cpu->r[S];
+
+    DO_PENDING_LOAD;
+
+    uint32_t addr = s + IMM16S;
+    uint32_t aligned = addr & ~0x3;
+    uint32_t v = psx_bus_read32(cpu->bus, aligned);
+
+    switch (addr & 0x3) {
+        case 0: v =                     cpu->r[T]       ; break;
+        case 1: v = (v & 0x000000ff) | (cpu->r[T] << 8 ); break;
+        case 2: v = (v & 0x0000ffff) | (cpu->r[T] << 16); break;
+        case 3: v = (v & 0x00ffffff) | (cpu->r[T] << 24); break;
+    }
+
+    psx_bus_write32(cpu->bus, aligned, v);
+}
+
+void psx_cpu_i_lwc0(psx_cpu_t* cpu) {
+    psx_cpu_exception(cpu, CAUSE_CPU);
+}
+
+void psx_cpu_i_lwc1(psx_cpu_t* cpu) {
+    psx_cpu_exception(cpu, CAUSE_CPU);
+}
+
+void psx_cpu_i_lwc3(psx_cpu_t* cpu) {
+    psx_cpu_exception(cpu, CAUSE_CPU);
+}
+
+void psx_cpu_i_swc0(psx_cpu_t* cpu) {
+    psx_cpu_exception(cpu, CAUSE_CPU);
+}
+
+void psx_cpu_i_swc1(psx_cpu_t* cpu) {
+    psx_cpu_exception(cpu, CAUSE_CPU);
+}
+
+void psx_cpu_i_swc3(psx_cpu_t* cpu) {
+    psx_cpu_exception(cpu, CAUSE_CPU);
+}
+
+// Secondary
+void psx_cpu_i_sll(psx_cpu_t* cpu) {
+    TRACE_I5D("sll");
+
+    uint32_t t = cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    cpu->r[D] = t << IMM5;
+}
+
+void psx_cpu_i_srl(psx_cpu_t* cpu) {
+    TRACE_I5D("srl");
+
+    uint32_t t = cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    cpu->r[D] = t >> IMM5;
+}
+
+void psx_cpu_i_sra(psx_cpu_t* cpu) {
+    TRACE_I5D("sra");
+
+    int32_t t = (int32_t)cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    cpu->r[D] = t >> IMM5;
+}
+
+void psx_cpu_i_sllv(psx_cpu_t* cpu) {
+    TRACE_RT("sllv");
+
+    uint32_t s = cpu->r[S];
+    uint32_t t = cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    cpu->r[D] = t << (s & 0x1f);
+}
+
+void psx_cpu_i_srlv(psx_cpu_t* cpu) {
+    TRACE_RT("srlv");
+
+    uint32_t s = cpu->r[S];
+    uint32_t t = cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    cpu->r[D] = t >> (s & 0x1f);
+}
+
+void psx_cpu_i_srav(psx_cpu_t* cpu) {
+    TRACE_RT("srav");
+
+    uint32_t s = cpu->r[S];
+    int32_t t = (int32_t)cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    cpu->r[D] = t >> (s & 0x1f);
+}
+
+void psx_cpu_i_jr(psx_cpu_t* cpu) {
+    cpu->branch = 1;
+
+    TRACE_RS("jr");
+
+    uint32_t s = cpu->r[S];
+
+    DO_PENDING_LOAD;
+
+    cpu->next_pc = s;
+}
+
+void psx_cpu_i_jalr(psx_cpu_t* cpu) {
+    cpu->branch = 1;
+
+    TRACE_RD("jalr");
+
+    uint32_t s = cpu->r[S];
+
+    DO_PENDING_LOAD;
+
+    cpu->r[D] = cpu->next_pc;
+
+    cpu->next_pc = s;
+}
+
+void psx_cpu_i_syscall(psx_cpu_t* cpu) {
+    TRACE_I20("syscall");
+    
+    DO_PENDING_LOAD;
+
+    psx_cpu_exception(cpu, CAUSE_SYSCALL);
+}
+
+void psx_cpu_i_break(psx_cpu_t* cpu) {
+    TRACE_I20("break");
+
+    DO_PENDING_LOAD;
+
+    psx_cpu_exception(cpu, CAUSE_BP);
+}
+
+void psx_cpu_i_mfhi(psx_cpu_t* cpu) {
+    TRACE_MTF("mfhi");
+
+    DO_PENDING_LOAD;
+
+    cpu->r[D] = cpu->hi;
+}
+
+void psx_cpu_i_mthi(psx_cpu_t* cpu) {
+    TRACE_MTF("mthi");
+
+    DO_PENDING_LOAD;
+
+    cpu->hi = cpu->r[S];
+}
+
+void psx_cpu_i_mflo(psx_cpu_t* cpu) {
+    TRACE_MTF("mflo");
+
+    DO_PENDING_LOAD;
+
+    cpu->r[D] = cpu->lo;
+}
+
+void psx_cpu_i_mtlo(psx_cpu_t* cpu) {
+    TRACE_MTF("mtlo");
+
+    DO_PENDING_LOAD;
+
+    cpu->lo = cpu->r[S];
+}
+
+void psx_cpu_i_mult(psx_cpu_t* cpu) {
+    TRACE_MD("mult");
+
+    int64_t s = (int64_t)((int32_t)cpu->r[S]);
+    int64_t t = (int64_t)((int32_t)cpu->r[T]);
+
+    DO_PENDING_LOAD;
+
+    uint64_t r = s * t;
+
+    cpu->hi = r >> 32;
+    cpu->lo = r & 0xffffffff;
+}
+
+void psx_cpu_i_multu(psx_cpu_t* cpu) {
+    TRACE_MD("multu");
+
+    uint64_t s = (uint64_t)cpu->r[S];
+    uint64_t t = (uint64_t)cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    uint64_t r = s * t;
+
+    cpu->hi = r >> 32;
+    cpu->lo = r & 0xffffffff;
+}
+
+void psx_cpu_i_div(psx_cpu_t* cpu) {
+    TRACE_MD("div");
+
+    int32_t s = (int32_t)cpu->r[S];
+    int32_t t = (int32_t)cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    if (!t) {
+        cpu->hi = s;
+        cpu->lo = (s >= 0) ? 0xffffffff : 1;
+    } else if ((((uint32_t)s) == 0x80000000) && (t == -1)) {
+        cpu->hi = 0;
+        cpu->lo = 0x80000000;
+    } else {
+        cpu->hi = (uint32_t)(s % t);
+        cpu->lo = (uint32_t)(s / t);
+    }
+}
+
+void psx_cpu_i_divu(psx_cpu_t* cpu) {
+    TRACE_MD("divu");
+
+    uint32_t s = cpu->r[S];
+    uint32_t t = cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    if (!t) {
+        cpu->hi = s;
+        cpu->lo = 0xffffffff;
+    } else {
+        cpu->hi = s % t;
+        cpu->lo = s / t;
+    }
+}
+
+void psx_cpu_i_add(psx_cpu_t* cpu) {
+    TRACE_RT("add");
+
+    int32_t s = cpu->r[S];
+    int32_t t = cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    int32_t r = s + t;
+    uint32_t o = (s ^ r) & (t ^ r);
+
+    if (o & 0x80000000) {
+        psx_cpu_exception(cpu, CAUSE_OV);
+    } else {
+        cpu->r[D] = (uint32_t)r;
+    }
+}
+
+void psx_cpu_i_addu(psx_cpu_t* cpu) {
+    TRACE_RT("addu");
+
+    uint32_t s = cpu->r[S];
+    uint32_t t = cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    cpu->r[D] = s + t;
+}
+
+void psx_cpu_i_sub(psx_cpu_t* cpu) {
+    TRACE_RT("sub");
+
+    int32_t s = (int32_t)cpu->r[S];
+    int32_t t = (int32_t)cpu->r[T];
+    int32_t r;
+
+    DO_PENDING_LOAD;
+
+    int o = __builtin_ssub_overflow(s, t, &r);
+
+    if (o) {
+        psx_cpu_exception(cpu, CAUSE_OV);
+    } else {
+        cpu->r[D] = r;
+    }
+}
+
+void psx_cpu_i_subu(psx_cpu_t* cpu) {
+    TRACE_RT("subu");
+
+    uint32_t s = cpu->r[S];
+    uint32_t t = cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    cpu->r[D] = s - t;
+}
+
+void psx_cpu_i_and(psx_cpu_t* cpu) {
+    TRACE_RT("and");
+
+    uint32_t s = cpu->r[S];
+    uint32_t t = cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    cpu->r[D] = s & t;
+}
+
+void psx_cpu_i_or(psx_cpu_t* cpu) {
+    TRACE_RT("or");
+
+    uint32_t s = cpu->r[S];
+    uint32_t t = cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    cpu->r[D] = s | t;
+}
+
+void psx_cpu_i_xor(psx_cpu_t* cpu) {
+    TRACE_RT("xor");
+
+    uint32_t s = cpu->r[S];
+    uint32_t t = cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    cpu->r[D] = (s ^ t);
+}
+
+void psx_cpu_i_nor(psx_cpu_t* cpu) {
+    TRACE_RT("nor");
+
+    uint32_t s = cpu->r[S];
+    uint32_t t = cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    cpu->r[D] = ~(s | t);
+}
+
+void psx_cpu_i_slt(psx_cpu_t* cpu) {
+    TRACE_RT("slt");
+
+    int32_t s = (int32_t)cpu->r[S];
+    int32_t t = (int32_t)cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    cpu->r[D] = s < t;
+}
+
+void psx_cpu_i_sltu(psx_cpu_t* cpu) {
+    TRACE_RT("sltu");
+
+    uint32_t s = cpu->r[S];
+    uint32_t t = cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    cpu->r[D] = s < t;
+}
+
+// COP0
+void psx_cpu_i_mfc0(psx_cpu_t* cpu) {
+    TRACE_C0M("mfc0");
+
+    DO_PENDING_LOAD;
+
+    cpu->load_v = cpu->cop0_r[D];
+    cpu->load_d = T;
+}
+
+void psx_cpu_i_mtc0(psx_cpu_t* cpu) {
+    TRACE_C0M("mtc0");
+
+    uint32_t t = cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    cpu->cop0_r[D] = t & g_psx_cpu_cop0_write_mask_table[D];
+}
+
+void psx_cpu_i_rfe(psx_cpu_t* cpu) {
+    TRACE_N("rfe");
+
+    DO_PENDING_LOAD;
+
+    uint32_t mode = cpu->cop0_r[COP0_SR] & 0x3f;
+
+    cpu->cop0_r[COP0_SR] &= 0xfffffff0;
+    cpu->cop0_r[COP0_SR] |= mode >> 2;
+}
+
+// COP2
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#define CLAMP(v, a, b) (((v) < (a)) ? (a) : (((v) > (b)) ? (b) : (v)))
+
+void gte_handle_irgb_write(psx_cpu_t* cpu) {
+    cpu->cop2_dr.ir[1] = ((cpu->cop2_dr.irgb >> 0) & 0x1f) * 0x80;
+    cpu->cop2_dr.ir[2] = ((cpu->cop2_dr.irgb >> 5) & 0x1f) * 0x80;
+    cpu->cop2_dr.ir[3] = ((cpu->cop2_dr.irgb >> 10) & 0x1f) * 0x80;
+}
+
+void gte_handle_irgb_read(psx_cpu_t* cpu) {
+    int r = CLAMP(cpu->cop2_dr.ir[1] >> 7, 0x00, 0x1f);
+    int g = CLAMP(cpu->cop2_dr.ir[2] >> 7, 0x00, 0x1f);
+    int b = CLAMP(cpu->cop2_dr.ir[3] >> 7, 0x00, 0x1f);
+
+    cpu->cop2_dr.irgb = r | (g << 5) | (b << 10);
+}
+
+void gte_handle_sxyp_write(psx_cpu_t* cpu) {
+    cpu->cop2_dr.sxy[0] = cpu->cop2_dr.sxy[1];
+    cpu->cop2_dr.sxy[1] = cpu->cop2_dr.sxy[2];
+    cpu->cop2_dr.sxy[2] = cpu->cop2_dr.sxy[3];
+}
+
+void gte_handle_lzcs_write(psx_cpu_t* cpu) {
+    if ((cpu->cop2_dr.lzcs == 0xffffffff) || !cpu->cop2_dr.lzcs) {
+        cpu->cop2_dr.lzcr = 32;
+
+        return;
+    }
+
+    int b = (cpu->cop2_dr.lzcs >> 31) & 1;
+
+    cpu->cop2_dr.lzcr = __builtin_clz(b ? ~cpu->cop2_dr.lzcs : cpu->cop2_dr.lzcs);
+}
+
+uint32_t gte_read_register(psx_cpu_t* cpu, uint32_t r) {
+    switch (r) {
+        case 0 : return cpu->cop2_dr.v[0].xy;
+        case 1 : return (int32_t)cpu->cop2_dr.v[0].z;
+        case 2 : return cpu->cop2_dr.v[1].xy;
+        case 3 : return (int32_t)cpu->cop2_dr.v[1].z;
+        case 4 : return cpu->cop2_dr.v[2].xy;
+        case 5 : return (int32_t)cpu->cop2_dr.v[2].z;
+        case 6 : return cpu->cop2_dr.rgbc.rgbc;
+        case 7 : return cpu->cop2_dr.otz;
+        case 8 : return (int32_t)cpu->cop2_dr.ir[0];
+        case 9 : return (int32_t)cpu->cop2_dr.ir[1];
+        case 10: return (int32_t)cpu->cop2_dr.ir[2];
+        case 11: return (int32_t)cpu->cop2_dr.ir[3];
+        case 12: return cpu->cop2_dr.sxy[0].xy;
+        case 13: return cpu->cop2_dr.sxy[1].xy;
+        case 14: return cpu->cop2_dr.sxy[2].xy;
+        case 15: return cpu->cop2_dr.sxy[2].xy; // SXY2 Mirror
+        case 16: return cpu->cop2_dr.sz[0];
+        case 17: return cpu->cop2_dr.sz[1];
+        case 18: return cpu->cop2_dr.sz[2];
+        case 19: return cpu->cop2_dr.sz[3];
+        case 20: return cpu->cop2_dr.rgb[0].rgbc;
+        case 21: return cpu->cop2_dr.rgb[1].rgbc;
+        case 22: return cpu->cop2_dr.rgb[2].rgbc;
+        case 23: return cpu->cop2_dr.res1;
+        case 24: return cpu->cop2_dr.mac[0];
+        case 25: return cpu->cop2_dr.mac[1];
+        case 26: return cpu->cop2_dr.mac[2];
+        case 27: return cpu->cop2_dr.mac[3];
+        case 28: gte_handle_irgb_read(cpu); return cpu->cop2_dr.irgb;
+        case 29: return cpu->cop2_dr.irgb; // IRGB mirror
+        case 30: return cpu->cop2_dr.lzcs;
+        case 31: return cpu->cop2_dr.lzcr;
+        case 32: return cpu->cop2_cr.rt.m[0].u32;
+        case 33: return cpu->cop2_cr.rt.m[1].u32;
+        case 34: return cpu->cop2_cr.rt.m[2].u32;
+        case 35: return cpu->cop2_cr.rt.m[3].u32;
+        case 36: return (int32_t)cpu->cop2_cr.rt.m33;
+        case 37: return cpu->cop2_cr.tr.x;
+        case 38: return cpu->cop2_cr.tr.y;
+        case 39: return cpu->cop2_cr.tr.z;
+        case 40: return cpu->cop2_cr.l.m[0].u32;
+        case 41: return cpu->cop2_cr.l.m[1].u32;
+        case 42: return cpu->cop2_cr.l.m[2].u32;
+        case 43: return cpu->cop2_cr.l.m[3].u32;
+        case 44: return (int32_t)cpu->cop2_cr.l.m33;
+        case 45: return cpu->cop2_cr.bk.x;
+        case 46: return cpu->cop2_cr.bk.y;
+        case 47: return cpu->cop2_cr.bk.z;
+        case 48: return cpu->cop2_cr.lr.m[0].u32;
+        case 49: return cpu->cop2_cr.lr.m[1].u32;
+        case 50: return cpu->cop2_cr.lr.m[2].u32;
+        case 51: return cpu->cop2_cr.lr.m[3].u32;
+        case 52: return (int32_t)cpu->cop2_cr.lr.m33;
+        case 53: return cpu->cop2_cr.fc.x;
+        case 54: return cpu->cop2_cr.fc.y;
+        case 55: return cpu->cop2_cr.fc.z;
+        case 56: return cpu->cop2_cr.ofx;
+        case 57: return cpu->cop2_cr.ofy;
+        case 58: return (int32_t)(int16_t)cpu->cop2_cr.h;
+        case 59: return cpu->cop2_cr.dqa;
+        case 60: return cpu->cop2_cr.dqb;
+        case 61: return cpu->cop2_cr.zsf3;
+        case 62: return cpu->cop2_cr.zsf4;
+        case 63: return (cpu->cop2_cr.flag & 0x7ffff000) | 
+                        (((cpu->cop2_cr.flag & 0x7f87e000) != 0) << 31);
+    }
+
+    return 0x00000000;
+}
+
+void gte_write_register(psx_cpu_t* cpu, uint32_t r, uint32_t value) {
+    switch (r) {
+        case 0 : cpu->cop2_dr.v[0].xy = value; break;
+        case 1 : cpu->cop2_dr.v[0].z = value; break;
+        case 2 : cpu->cop2_dr.v[1].xy = value; break;
+        case 3 : cpu->cop2_dr.v[1].z = value; break;
+        case 4 : cpu->cop2_dr.v[2].xy = value; break;
+        case 5 : cpu->cop2_dr.v[2].z = value; break;
+        case 6 : cpu->cop2_dr.rgbc.rgbc = value; break;
+        case 7 : cpu->cop2_dr.otz = value; break;
+        case 8 : cpu->cop2_dr.ir[0] = value; break;
+        case 9 : cpu->cop2_dr.ir[1] = value; break;
+        case 10: cpu->cop2_dr.ir[2] = value; break;
+        case 11: cpu->cop2_dr.ir[3] = value; break;
+        case 12: cpu->cop2_dr.sxy[0].xy = value; break;
+        case 13: cpu->cop2_dr.sxy[1].xy = value; break;
+        case 14: cpu->cop2_dr.sxy[2].xy = value; break;
+        case 15: cpu->cop2_dr.sxy[3].xy = value; gte_handle_sxyp_write(cpu); break;
+        case 16: cpu->cop2_dr.sz[0] = value; break;
+        case 17: cpu->cop2_dr.sz[1] = value; break;
+        case 18: cpu->cop2_dr.sz[2] = value; break;
+        case 19: cpu->cop2_dr.sz[3] = value; break;
+        case 20: cpu->cop2_dr.rgb[0].rgbc = value; break;
+        case 21: cpu->cop2_dr.rgb[1].rgbc = value; break;
+        case 22: cpu->cop2_dr.rgb[2].rgbc = value; break;
+        case 23: cpu->cop2_dr.res1 = value; break;
+        case 24: cpu->cop2_dr.mac[0] = value; break;
+        case 25: cpu->cop2_dr.mac[1] = value; break;
+        case 26: cpu->cop2_dr.mac[2] = value; break;
+        case 27: cpu->cop2_dr.mac[3] = value; break;
+        case 28: cpu->cop2_dr.irgb = value & 0x7fff; gte_handle_irgb_write(cpu); break;
+        case 29: /* ORGB RO */ break;
+        case 30: cpu->cop2_dr.lzcs = value; gte_handle_lzcs_write(cpu); break;
+        case 31: /* LZCR RO */ break;
+        case 32: cpu->cop2_cr.rt.m[0].u32 = value; break;
+        case 33: cpu->cop2_cr.rt.m[1].u32 = value; break;
+        case 34: cpu->cop2_cr.rt.m[2].u32 = value; break;
+        case 35: cpu->cop2_cr.rt.m[3].u32 = value; break;
+        case 36: cpu->cop2_cr.rt.m33 = value; break;
+        case 37: cpu->cop2_cr.tr.x = value; break;
+        case 38: cpu->cop2_cr.tr.y = value; break;
+        case 39: cpu->cop2_cr.tr.z = value; break;
+        case 40: cpu->cop2_cr.l.m[0].u32 = value; break;
+        case 41: cpu->cop2_cr.l.m[1].u32 = value; break;
+        case 42: cpu->cop2_cr.l.m[2].u32 = value; break;
+        case 43: cpu->cop2_cr.l.m[3].u32 = value; break;
+        case 44: cpu->cop2_cr.l.m33 = value; break;
+        case 45: cpu->cop2_cr.bk.x = value; break;
+        case 46: cpu->cop2_cr.bk.y = value; break;
+        case 47: cpu->cop2_cr.bk.z = value; break;
+        case 48: cpu->cop2_cr.lr.m[0].u32 = value; break;
+        case 49: cpu->cop2_cr.lr.m[1].u32 = value; break;
+        case 50: cpu->cop2_cr.lr.m[2].u32 = value; break;
+        case 51: cpu->cop2_cr.lr.m[3].u32 = value; break;
+        case 52: cpu->cop2_cr.lr.m33 = value; break;
+        case 53: cpu->cop2_cr.fc.x = value; break;
+        case 54: cpu->cop2_cr.fc.y = value; break;
+        case 55: cpu->cop2_cr.fc.z = value; break;
+        case 56: cpu->cop2_cr.ofx = value; break;
+        case 57: cpu->cop2_cr.ofy = value; break;
+        case 58: cpu->cop2_cr.h = value; break;
+        case 59: cpu->cop2_cr.dqa = value; break;
+        case 60: cpu->cop2_cr.dqb = value; break;
+        case 61: cpu->cop2_cr.zsf3 = value; break;
+        case 62: cpu->cop2_cr.zsf4 = value; break;
+        case 63: cpu->cop2_cr.flag = value & 0x7ffff000; break;
+    }
+}
+
+void psx_cpu_i_lwc2(psx_cpu_t* cpu) {
+    uint32_t s = cpu->r[S];
+    uint32_t addr = s + IMM16S;
+
+    DO_PENDING_LOAD;
+
+    if (addr & 0x3) {
+        psx_cpu_exception(cpu, CAUSE_ADEL);
+    } else {
+        gte_write_register(cpu, T, psx_bus_read32(cpu->bus, addr));
+    }
+}
+
+void psx_cpu_i_swc2(psx_cpu_t* cpu) {
+    uint32_t s = cpu->r[S];
+    uint32_t addr = s + IMM16S;
+
+    DO_PENDING_LOAD;
+
+    // Cache isolated
+    if (cpu->cop0_r[COP0_SR] & SR_ISC) {
+        log_debug("Ignoring write while cache is isolated");
+
+        return;
+    }
+
+    if (addr & 0x3) {
+        psx_cpu_exception(cpu, CAUSE_ADES);
+    } else {
+        psx_bus_write32(cpu->bus, addr, gte_read_register(cpu, T));
+    }
+}
+
+void psx_cpu_i_mfc2(psx_cpu_t* cpu) {
+    TRACE_C2M("mfc2");
+
+    DO_PENDING_LOAD;
+
+    cpu->load_v = gte_read_register(cpu, D);
+    cpu->load_d = T;
+}
+
+void psx_cpu_i_cfc2(psx_cpu_t* cpu) {
+    TRACE_C2MC("cfc2");
+
+    DO_PENDING_LOAD;
+
+    cpu->load_v = gte_read_register(cpu, D + 32);
+    cpu->load_d = T;
+}
+
+void psx_cpu_i_mtc2(psx_cpu_t* cpu) {
+    TRACE_C2M("mtc2");
+
+    uint32_t t = cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    gte_write_register(cpu, D, t);
+}
+
+void psx_cpu_i_ctc2(psx_cpu_t* cpu) {
+    TRACE_C2MC("ctc2");
+
+    uint32_t t = cpu->r[T];
+
+    DO_PENDING_LOAD;
+
+    gte_write_register(cpu, D + 32, t);
+}
+
+#define R_FLAG cpu->cop2_cr.flag
+
+int64_t gte_clamp_mac0(psx_cpu_t* cpu, int64_t value) {
+    cpu->s_mac0 = value;
+
+    if (value < (-0x80000000ll)) {
+        R_FLAG |= 0x8000;
+    } else if (value > (0x7fffffffll)) {
+        R_FLAG |= 0x10000;
+    }
+
+    return value;
+}
+
+int32_t gte_clamp_mac(psx_cpu_t* cpu, int i, int64_t value) {
+    if (i == 3)
+        cpu->s_mac3 = value;
+
+    if (value < -0x80000000000) {
+        R_FLAG |= 0x8000000 >> (i - 1);
+    } else if (value > 0x7ffffffffff) {
+        R_FLAG |= 0x40000000 >> (i - 1);
+    }
+
+    return (int32_t)(((value << 20) >> 20) >> cpu->gte_sf);
+}
+
+int32_t gte_clamp_ir0(psx_cpu_t* cpu, int32_t value) {
+    if (value < 0) {
+        R_FLAG |= 0x1000;
+
+        return 0;
+    } else if (value > 0x1000) {
+        R_FLAG |= 0x1000;
+
+        return 0x1000;
+    }
+
+    return value;
+}
+
+int64_t gte_clamp_sxy(psx_cpu_t* cpu, int i, int64_t value) {
+    if (value < -0x400) {
+        R_FLAG |= (uint32_t)(0x4000 >> (i - 1));
+
+        return -0x400;
+    } else if (value > 0x3ff) {
+        R_FLAG |= (uint32_t)(0x4000 >> (i - 1));
+
+        return 0x3ff;
+    }
+
+    return value;
+}
+
+int32_t gte_clamp_sz3(psx_cpu_t* cpu, int32_t value) {
+    if (value < 0) {
+        R_FLAG |= 0x40000;
+
+        return 0;
+    } else if (value > 0xffff) {
+        R_FLAG |= 0x40000;
+
+        return 0xffff;
+    }
+
+    return value;
+}
+
+uint8_t gte_clamp_rgb(psx_cpu_t* cpu, int i, int value) {
+    if (value < 0) {
+        R_FLAG |= (uint32_t)0x200000 >> (i - 1);
+
+        return 0;
+    } else if (value > 0xff) {
+        R_FLAG |= (uint32_t)0x200000 >> (i - 1);
+
+        return 0xff;
+    }
+
+    return (uint8_t)value;
+}
+
+int32_t gte_clamp_ir(psx_cpu_t* cpu, int i, int value, int lm) {
+    if (lm && (value < 0)) {
+        R_FLAG |= (uint32_t)(0x1000000 >> (i - 1));
+
+        return 0;
+    } else if ((value < -0x8000) && !lm) {
+        R_FLAG |= (uint32_t)(0x1000000 >> (i - 1));
+
+        return -0x8000;
+    } else if (value > 0x7fff) {
+        R_FLAG |= (uint32_t)(0x1000000 >> (i - 1));
+
+        return 0x7fff;
+    }
+
+    return (int32_t)value;
+}
+
+int32_t gte_clamp_ir_z(psx_cpu_t* cpu, int64_t value, int sf, int lm) {
+    int32_t value_sf = value >> sf;
+    int32_t value_12 = value >> 12;
+    int32_t min = 0;
+
+    if (lm == 0)
+        min = -((int32_t)0x8000);
+
+    if (value_12 < (-((int32_t)0x8000)) || value_12 > 0x7fffl)
+        R_FLAG |= (1 << 22);
+
+    return (int32_t)CLAMP(value_sf, min, 0x7fffl);
+}
+
+int clz(uint32_t value) {
+    if (!value)
+        return 32;
+    
+    return __builtin_clz(value);
+}
+
+uint32_t gte_divide(psx_cpu_t* cpu, uint16_t n, uint16_t d) {
+    // Overflow
+    if (n >= d * 2) {
+        R_FLAG |= (1 << 31) | (1 << 17);
+
+        return 0x1ffff;
+    }
+
+    int shift = clz(d) - 16;
+
+    int r1 = (d << shift) & 0x7fff;
+    int r2 = g_psx_gte_unr_table[((r1 + 0x40) >> 7)] + 0x101;
+    int r3 = ((0x80 - (r2 * (r1 + 0x8000))) >> 8) & 0x1ffff;
+
+    uint32_t reciprocal = ((r2 * r3) + 0x80) >> 8;
+    uint32_t res = ((((uint64_t)reciprocal * (n << shift)) + 0x8000) >> 16);
+
+    return MIN(0x1ffff, res);
+}
+
+void psx_cpu_i_gte(psx_cpu_t* cpu) {
+    DO_PENDING_LOAD;
+
+    cpu->gte_sf = ((cpu->opcode & 0x80000) != 0) * 12;
+    cpu->gte_lm = (cpu->opcode & 0x400) != 0;
+    cpu->gte_cv = (cpu->opcode >> 13) & 3;
+    cpu->gte_v  = (cpu->opcode >> 15) & 3;
+    cpu->gte_mx = (cpu->opcode >> 17) & 3;
+
+    g_psx_gte_table[cpu->opcode & 0x3f](cpu);
+}
+
+void psx_gte_i_invalid(psx_cpu_t* cpu) {
+    log_fatal("invalid: Unimplemented GTE instruction %02x, %02x", cpu->opcode & 0x3f, cpu->opcode >> 25);
+}
+
+#define I64(v) ((int64_t)v)
+#define R_TRX cpu->cop2_cr.tr.x
+#define R_TRY cpu->cop2_cr.tr.y
+#define R_TRZ cpu->cop2_cr.tr.z
+#define R_RT11 cpu->cop2_cr.rt.m[0].c[0]
+#define R_RT11 cpu->cop2_cr.rt.m[0].c[0]
+#define R_RT12 cpu->cop2_cr.rt.m[0].c[1]
+#define R_RT13 cpu->cop2_cr.rt.m[1].c[0]
+#define R_RT21 cpu->cop2_cr.rt.m[1].c[1]
+#define R_RT22 cpu->cop2_cr.rt.m[2].c[0]
+#define R_RT23 cpu->cop2_cr.rt.m[2].c[1]
+#define R_RT31 cpu->cop2_cr.rt.m[3].c[0]
+#define R_RT32 cpu->cop2_cr.rt.m[3].c[1]
+#define R_RT33 cpu->cop2_cr.rt.m33
+#define R_MAC0 cpu->cop2_dr.mac[0]
+#define R_MAC1 cpu->cop2_dr.mac[1]
+#define R_MAC2 cpu->cop2_dr.mac[2]
+#define R_MAC3 cpu->cop2_dr.mac[3]
+#define R_OFX cpu->cop2_cr.ofx
+#define R_OFY cpu->cop2_cr.ofy
+#define R_IR0 cpu->cop2_dr.ir[0]
+#define R_IR1 cpu->cop2_dr.ir[1]
+#define R_IR2 cpu->cop2_dr.ir[2]
+#define R_IR3 cpu->cop2_dr.ir[3]
+#define R_SXY0 cpu->cop2_dr.sxy[0].xy
+#define R_SX0 cpu->cop2_dr.sxy[0].p[0]
+#define R_SY0 cpu->cop2_dr.sxy[0].p[1]
+#define R_SZ0 cpu->cop2_dr.sz[0]
+#define R_SXY1 cpu->cop2_dr.sxy[1].xy
+#define R_SX1 cpu->cop2_dr.sxy[1].p[0]
+#define R_SY1 cpu->cop2_dr.sxy[1].p[1]
+#define R_SZ1 cpu->cop2_dr.sz[1]
+#define R_SXY2 cpu->cop2_dr.sxy[2].xy
+#define R_SX2 cpu->cop2_dr.sxy[2].p[0]
+#define R_SY2 cpu->cop2_dr.sxy[2].p[1]
+#define R_SZ2 cpu->cop2_dr.sz[2]
+#define R_SZ3 cpu->cop2_dr.sz[3]
+#define R_DQA cpu->cop2_cr.dqa
+#define R_DQB cpu->cop2_cr.dqb
+#define R_ZSF3 cpu->cop2_cr.zsf3
+#define R_ZSF4 cpu->cop2_cr.zsf4
+#define R_OTZ cpu->cop2_dr.otz
+#define R_H cpu->cop2_cr.h
+#define R_RC cpu->cop2_dr.rgbc.c[0]
+#define R_GC cpu->cop2_dr.rgbc.c[1]
+#define R_BC cpu->cop2_dr.rgbc.c[2]
+#define R_CODE cpu->cop2_dr.rgbc.c[3]
+#define R_RGBC cpu->cop2_dr.rgbc.rgbc
+#define R_RFC cpu->cop2_cr.fc.x
+#define R_GFC cpu->cop2_cr.fc.y
+#define R_BFC cpu->cop2_cr.fc.z
+#define R_RGB0 cpu->cop2_dr.rgb[0].rgbc
+#define R_RGB1 cpu->cop2_dr.rgb[1].rgbc
+#define R_RGB2 cpu->cop2_dr.rgb[2].rgbc
+#define R_RC0 cpu->cop2_dr.rgb[0].c[0]
+#define R_GC0 cpu->cop2_dr.rgb[0].c[1]
+#define R_BC0 cpu->cop2_dr.rgb[0].c[2]
+#define R_CD0 cpu->cop2_dr.rgb[0].c[3]
+#define R_RC1 cpu->cop2_dr.rgb[1].c[0]
+#define R_GC1 cpu->cop2_dr.rgb[1].c[1]
+#define R_BC1 cpu->cop2_dr.rgb[1].c[2]
+#define R_CD1 cpu->cop2_dr.rgb[1].c[3]
+#define R_RC2 cpu->cop2_dr.rgb[2].c[0]
+#define R_GC2 cpu->cop2_dr.rgb[2].c[1]
+#define R_BC2 cpu->cop2_dr.rgb[2].c[2]
+#define R_CD2 cpu->cop2_dr.rgb[2].c[3]
+#define R_L11 cpu->cop2_cr.l.m[0].c[0]
+#define R_L12 cpu->cop2_cr.l.m[0].c[1]
+#define R_L13 cpu->cop2_cr.l.m[1].c[0]
+#define R_L21 cpu->cop2_cr.l.m[1].c[1]
+#define R_L22 cpu->cop2_cr.l.m[2].c[0]
+#define R_L23 cpu->cop2_cr.l.m[2].c[1]
+#define R_L31 cpu->cop2_cr.l.m[3].c[0]
+#define R_L32 cpu->cop2_cr.l.m[3].c[1]
+#define R_L33 cpu->cop2_cr.l.m33
+#define R_RBK cpu->cop2_cr.bk.x
+#define R_GBK cpu->cop2_cr.bk.y
+#define R_BBK cpu->cop2_cr.bk.z
+#define R_LR1 cpu->cop2_cr.lr.m[0].c[0]
+#define R_LR2 cpu->cop2_cr.lr.m[0].c[1]
+#define R_LR3 cpu->cop2_cr.lr.m[1].c[0]
+#define R_LG1 cpu->cop2_cr.lr.m[1].c[1]
+#define R_LG2 cpu->cop2_cr.lr.m[2].c[0]
+#define R_LG3 cpu->cop2_cr.lr.m[2].c[1]
+#define R_LB1 cpu->cop2_cr.lr.m[3].c[0]
+#define R_LB2 cpu->cop2_cr.lr.m[3].c[1]
+#define R_LB3 cpu->cop2_cr.lr.m33
+
+#define GTE_RTP_DQ(i) { \
+    R_FLAG = 0; \
+    int64_t vx = (int64_t)((int16_t)cpu->cop2_dr.v[i].p[0]); \
+    int64_t vy = (int64_t)((int16_t)cpu->cop2_dr.v[i].p[1]); \
+    int64_t vz = (int64_t)cpu->cop2_dr.v[i].z; \
+    R_MAC1 = gte_clamp_mac(cpu, 1, (((int64_t)R_TRX) << 12) + (I64((int16_t)R_RT11) * vx) + (I64((int16_t)R_RT12) * vy) + (I64((int16_t)R_RT13) * vz)); \
+    R_MAC2 = gte_clamp_mac(cpu, 2, (((int64_t)R_TRY) << 12) + (I64((int16_t)R_RT21) * vx) + (I64((int16_t)R_RT22) * vy) + (I64((int16_t)R_RT23) * vz)); \
+    R_MAC3 = gte_clamp_mac(cpu, 3, (((int64_t)R_TRZ) << 12) + (I64((int16_t)R_RT31) * vx) + (I64((int16_t)R_RT32) * vy) + (I64((int16_t)R_RT33) * vz)); \
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm); \
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm); \
+    R_IR3 = gte_clamp_ir_z(cpu, cpu->s_mac3, cpu->gte_sf, cpu->gte_lm); \
+    R_SZ0 = R_SZ1; \
+    R_SZ1 = R_SZ2; \
+    R_SZ2 = R_SZ3; \
+    R_SZ3 = gte_clamp_sz3(cpu, cpu->s_mac3 >> 12); \
+    int32_t div = gte_divide(cpu, R_H, R_SZ3); \
+    R_SXY0 = R_SXY1; \
+    R_SXY1 = R_SXY2; \
+    R_SX2 = gte_clamp_sxy(cpu, 1, (gte_clamp_mac0(cpu, (int64_t)((int32_t)R_OFX) + ((int64_t)R_IR1 * div)) >> 16)); \
+    R_SY2 = gte_clamp_sxy(cpu, 2, (gte_clamp_mac0(cpu, (int64_t)((int32_t)R_OFY) + ((int64_t)R_IR2 * div)) >> 16)); \
+    R_MAC0 = gte_clamp_mac0(cpu, ((int64_t)R_DQB) + (((int64_t)R_DQA) * div)); \
+    R_IR0 = gte_clamp_ir0(cpu, cpu->s_mac0 >> 12); }
+
+#define GTE_RTP(i) { \
+    R_FLAG = 0; \
+    int64_t vx = (int64_t)((int16_t)cpu->cop2_dr.v[i].p[0]); \
+    int64_t vy = (int64_t)((int16_t)cpu->cop2_dr.v[i].p[1]); \
+    int64_t vz = (int64_t)cpu->cop2_dr.v[i].z; \
+    R_MAC1 = gte_clamp_mac(cpu, 1, (((int64_t)R_TRX) << 12) + (I64((int16_t)R_RT11) * vx) + (I64((int16_t)R_RT12) * vy) + (I64((int16_t)R_RT13) * vz)); \
+    R_MAC2 = gte_clamp_mac(cpu, 2, (((int64_t)R_TRY) << 12) + (I64((int16_t)R_RT21) * vx) + (I64((int16_t)R_RT22) * vy) + (I64((int16_t)R_RT23) * vz)); \
+    R_MAC3 = gte_clamp_mac(cpu, 3, (((int64_t)R_TRZ) << 12) + (I64((int16_t)R_RT31) * vx) + (I64((int16_t)R_RT32) * vy) + (I64((int16_t)R_RT33) * vz)); \
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm); \
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm); \
+    R_IR3 = gte_clamp_ir_z(cpu, cpu->s_mac3, cpu->gte_sf, cpu->gte_lm); \
+    R_SZ0 = R_SZ1; \
+    R_SZ1 = R_SZ2; \
+    R_SZ2 = R_SZ3; \
+    R_SZ3 = gte_clamp_sz3(cpu, cpu->s_mac3 >> 12); \
+    int32_t div = gte_divide(cpu, R_H, R_SZ3); \
+    R_SXY0 = R_SXY1; \
+    R_SXY1 = R_SXY2; \
+    R_SX2 = gte_clamp_sxy(cpu, 1, (gte_clamp_mac0(cpu, (int64_t)((int32_t)R_OFX) + ((int64_t)R_IR1 * div)) >> 16)); \
+    R_SY2 = gte_clamp_sxy(cpu, 2, (gte_clamp_mac0(cpu, (int64_t)((int32_t)R_OFY) + ((int64_t)R_IR2 * div)) >> 16)); }
+
+#define DPCT1 { \
+    R_FLAG = 0; \
+    int64_t mac1 = gte_clamp_mac(cpu, 1, (((int64_t)R_RFC) << 12) - (((int64_t)cpu->cop2_dr.rgb[0].c[0]) << 16)); \
+    int64_t mac2 = gte_clamp_mac(cpu, 2, (((int64_t)R_GFC) << 12) - (((int64_t)cpu->cop2_dr.rgb[0].c[1]) << 16)); \
+    int64_t mac3 = gte_clamp_mac(cpu, 3, (((int64_t)R_BFC) << 12) - (((int64_t)cpu->cop2_dr.rgb[0].c[2]) << 16)); \
+    int64_t ir1 = gte_clamp_ir(cpu, 1, mac1, 0); \
+    int64_t ir2 = gte_clamp_ir(cpu, 2, mac2, 0); \
+    int64_t ir3 = gte_clamp_ir(cpu, 3, mac3, 0); \
+    R_MAC1 = gte_clamp_mac(cpu, 1, (((int64_t)cpu->cop2_dr.rgb[0].c[0]) << 16) + (R_IR0 * ir1)); \
+    R_MAC2 = gte_clamp_mac(cpu, 2, (((int64_t)cpu->cop2_dr.rgb[0].c[1]) << 16) + (R_IR0 * ir2)); \
+    R_MAC3 = gte_clamp_mac(cpu, 3, (((int64_t)cpu->cop2_dr.rgb[0].c[2]) << 16) + (R_IR0 * ir3)); \
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm); \
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm); \
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm); \
+    R_RGB0 = R_RGB1; \
+    R_RGB1 = R_RGB2; \
+    R_CD2 = R_CODE; \
+    R_RC2 = gte_clamp_rgb(cpu, 1, R_MAC1 >> 4); \
+    R_GC2 = gte_clamp_rgb(cpu, 2, R_MAC2 >> 4); \
+    R_BC2 = gte_clamp_rgb(cpu, 3, R_MAC3 >> 4); }
+
+#define NCCS(i) { \
+    R_FLAG = 0; \
+    int64_t vx = (int64_t)((int16_t)cpu->cop2_dr.v[i].p[0]); \
+    int64_t vy = (int64_t)((int16_t)cpu->cop2_dr.v[i].p[1]); \
+    int64_t vz = (int64_t)cpu->cop2_dr.v[i].z; \
+    R_MAC1 = (int)(gte_clamp_mac(cpu, 1, (int64_t)R_L11 * vx + R_L12 * vy + R_L13 * vz)); \
+    R_MAC2 = (int)(gte_clamp_mac(cpu, 2, (int64_t)R_L21 * vx + R_L22 * vy + R_L23 * vz)); \
+    R_MAC3 = (int)(gte_clamp_mac(cpu, 3, (int64_t)R_L31 * vx + R_L32 * vy + R_L33 * vz)); \
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm); \
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm); \
+    R_IR3 = gte_clamp_ir_z(cpu, cpu->s_mac3, cpu->gte_sf, cpu->gte_lm); \
+    R_MAC1 = (int)(gte_clamp_mac(cpu, 1, gte_clamp_mac(cpu, 1, gte_clamp_mac(cpu, 1, (long)R_RBK * 0x1000 + R_LR1 * R_IR1) + (long)R_LG1 * R_IR2) + (long)R_LB1 * R_IR3)); \
+    R_MAC2 = (int)(gte_clamp_mac(cpu, 2, gte_clamp_mac(cpu, 2, gte_clamp_mac(cpu, 2, (long)R_GBK * 0x1000 + R_LR2 * R_IR1) + (long)R_LG2 * R_IR2) + (long)R_LB2 * R_IR3)); \
+    R_MAC3 = (int)(gte_clamp_mac(cpu, 3, gte_clamp_mac(cpu, 3, gte_clamp_mac(cpu, 3, (long)R_BBK * 0x1000 + R_LR3 * R_IR1) + (long)R_LG3 * R_IR2) + (long)R_LB3 * R_IR3)); \
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm); \
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm); \
+    R_IR3 = gte_clamp_ir_z(cpu, cpu->s_mac3, cpu->gte_sf, cpu->gte_lm); \
+    R_MAC1 = (int)gte_clamp_mac(cpu, 1, (R_RGB0 * R_IR1) << 4); \
+    R_MAC2 = (int)gte_clamp_mac(cpu, 2, (R_RGB1 * R_IR2) << 4); \
+    R_MAC3 = (int)gte_clamp_mac(cpu, 3, (R_RGB2 * R_IR3) << 4); \
+    R_MAC1 = (int)gte_clamp_mac(cpu, 1, R_MAC1); \
+    R_MAC2 = (int)gte_clamp_mac(cpu, 2, R_MAC2); \
+    R_MAC3 = (int)gte_clamp_mac(cpu, 3, R_MAC3); \
+    R_RGB0 = R_RGB1; \
+    R_RGB1 = R_RGB2; \
+    R_CD2 = R_CODE; \
+    R_RC2 = gte_clamp_rgb(cpu, 1, R_MAC1 >> 4); \
+    R_GC2 = gte_clamp_rgb(cpu, 2, R_MAC2 >> 4); \
+    R_BC2 = gte_clamp_rgb(cpu, 3, R_MAC3 >> 4); \
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm); \
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm); \
+    R_IR3 = gte_clamp_ir_z(cpu, cpu->s_mac3, cpu->gte_sf, cpu->gte_lm); }
+
+void psx_gte_i_rtps(psx_cpu_t* cpu) {
+    GTE_RTP_DQ(0);
+}
+
+void psx_gte_i_nclip(psx_cpu_t* cpu) {
+    R_FLAG = 0;
+
+    int64_t value = I64((int16_t)R_SX0) * (I64((int16_t)R_SY1) - I64((int16_t)R_SY2));
+    value += I64((int16_t)R_SX1) * (I64((int16_t)R_SY2) - I64((int16_t)R_SY0));
+    value += I64((int16_t)R_SX2) * (I64((int16_t)R_SY0) - I64((int16_t)R_SY1));
+
+    R_MAC0 = (int)gte_clamp_mac0(cpu, value);
+}
+
+void psx_gte_i_op(psx_cpu_t* cpu) {
+    R_FLAG = 0;
+
+    R_MAC1 = gte_clamp_mac(cpu, 1, I64(I64((int16_t)R_RT22) * I64(R_IR3)) - I64((I64((int16_t)R_RT33) * I64(R_IR2))));
+    R_MAC2 = gte_clamp_mac(cpu, 2, I64(I64((int16_t)R_RT33) * I64(R_IR1)) - I64((I64((int16_t)R_RT11) * I64(R_IR3))));
+    R_MAC3 = gte_clamp_mac(cpu, 3, I64(I64((int16_t)R_RT11) * I64(R_IR2)) - I64((I64((int16_t)R_RT22) * I64(R_IR1))));
+
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
+}
+
+void psx_gte_i_dpcs(psx_cpu_t* cpu) {
+    R_FLAG = 0;
+
+    int64_t mac1 = gte_clamp_mac(cpu, 1, (((int64_t)R_RFC) << 12) - (((int64_t)R_RC) << 16));
+    int64_t mac2 = gte_clamp_mac(cpu, 2, (((int64_t)R_GFC) << 12) - (((int64_t)R_GC) << 16));
+    int64_t mac3 = gte_clamp_mac(cpu, 3, (((int64_t)R_BFC) << 12) - (((int64_t)R_BC) << 16));
+
+    int64_t ir1 = gte_clamp_ir(cpu, 1, mac1, 0);
+    int64_t ir2 = gte_clamp_ir(cpu, 2, mac2, 0);
+    int64_t ir3 = gte_clamp_ir(cpu, 3, mac3, 0);
+
+    R_MAC1 = gte_clamp_mac(cpu, 1, (((int64_t)R_RC) << 16) + (R_IR0 * ir1));
+    R_MAC2 = gte_clamp_mac(cpu, 2, (((int64_t)R_GC) << 16) + (R_IR0 * ir2));
+    R_MAC3 = gte_clamp_mac(cpu, 3, (((int64_t)R_BC) << 16) + (R_IR0 * ir3));
+
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
+
+    R_RGB0 = R_RGB1;
+    R_RGB1 = R_RGB2;
+    R_CD2 = R_CODE;
+
+    R_RC2 = gte_clamp_rgb(cpu, 1, R_MAC1 >> 4);
+    R_GC2 = gte_clamp_rgb(cpu, 2, R_MAC2 >> 4);
+    R_BC2 = gte_clamp_rgb(cpu, 3, R_MAC3 >> 4);
+}
+
+void psx_gte_i_intpl(psx_cpu_t* cpu) {
+    R_FLAG = 0;
+
+    int64_t mac1 = gte_clamp_mac(cpu, 1, (((int64_t)R_RFC) << 12) - (I64(R_IR1) << 12));
+    int64_t mac2 = gte_clamp_mac(cpu, 2, (((int64_t)R_GFC) << 12) - (I64(R_IR2) << 12));
+    int64_t mac3 = gte_clamp_mac(cpu, 3, (((int64_t)R_BFC) << 12) - (I64(R_IR3) << 12));
+
+    int64_t ir1 = gte_clamp_ir(cpu, 1, mac1, 0);
+    int64_t ir2 = gte_clamp_ir(cpu, 2, mac2, 0);
+    int64_t ir3 = gte_clamp_ir(cpu, 3, mac3, 0);
+
+    R_MAC1 = gte_clamp_mac(cpu, 1, (I64(R_IR1) << 12) + (I64(R_IR0) * ir1));
+    R_MAC2 = gte_clamp_mac(cpu, 2, (I64(R_IR2) << 12) + (I64(R_IR0) * ir2));
+    R_MAC3 = gte_clamp_mac(cpu, 3, (I64(R_IR3) << 12) + (I64(R_IR0) * ir3));
+
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
+
+    R_RGB0 = R_RGB1;
+    R_RGB1 = R_RGB2;
+    R_CD2 = R_CODE;
+
+    R_RC2 = gte_clamp_rgb(cpu, 1, R_MAC1 >> 4);
+    R_GC2 = gte_clamp_rgb(cpu, 2, R_MAC2 >> 4);
+    R_BC2 = gte_clamp_rgb(cpu, 3, R_MAC3 >> 4);
+}
+
+#define R_VX v.p[0]
+#define R_VY v.p[1]
+#define R_VZ v.z
+#define R_MX11 mx.m[0].c[0]
+#define R_MX11 mx.m[0].c[0]
+#define R_MX12 mx.m[0].c[1]
+#define R_MX13 mx.m[1].c[0]
+#define R_MX21 mx.m[1].c[1]
+#define R_MX22 mx.m[2].c[0]
+#define R_MX23 mx.m[2].c[1]
+#define R_MX31 mx.m[3].c[0]
+#define R_MX32 mx.m[3].c[1]
+#define R_MX33 mx.m33
+#define R_CV1 cv.x
+#define R_CV2 cv.y
+#define R_CV3 cv.z
+
+void psx_gte_i_mvmva(psx_cpu_t* cpu) {
+    R_FLAG = 0;
+
+    gte_matrix_t mx = { 0 };
+    gte_vertex_t v = { 0 };
+    gte_vec3_t cv = { 0 };
+
+    switch (cpu->gte_mx) {
+        case 0: mx = cpu->cop2_cr.rt; break;
+        case 1: mx = cpu->cop2_cr.l; break;
+        case 2: mx = cpu->cop2_cr.lr; break;
+        case 3: {
+            R_MX11 = -R_RC << 4;
+            R_MX12 = R_RC << 4;
+            R_MX13 = R_IR0;
+            R_MX21 = R_RT13;
+            R_MX22 = R_RT13;
+            R_MX23 = R_RT13;
+            R_MX31 = R_RT22;
+            R_MX32 = R_RT22;
+            R_MX33 = R_RT22;
+        } break;
+    }
+
+    switch (cpu->gte_v) {
+        case 0: case 1: case 2:
+            v = cpu->cop2_dr.v[cpu->gte_v];
+        break;
+
+        case 3: {
+            v.p[0] = R_IR1;
+            v.p[1] = R_IR2;
+            v.z = R_IR3;
+        } break;
+    }
+
+    switch (cpu->gte_cv) {
+        case 0: cv = cpu->cop2_cr.tr; break;
+        case 1: cv = cpu->cop2_cr.bk; break;
+        case 2: cv = cpu->cop2_cr.fc; break;
+        case 3: {
+            cv.x = 0;
+            cv.y = 0;
+            cv.z = 0;
+        } break;
+    }
+
+    // Bugged case (CV=FC)
+    if (cpu->gte_cv == 2) {
+        R_MAC1 = gte_clamp_mac(cpu, 1, (int64_t)(I64(R_MX12) * I64(R_VY)) + (I64(R_MX13) * I64(R_VZ)));
+        R_MAC2 = gte_clamp_mac(cpu, 2, (int64_t)(I64(R_MX22) * I64(R_VY)) + (I64(R_MX23) * I64(R_VZ)));
+        R_MAC3 = gte_clamp_mac(cpu, 3, (int64_t)(I64(R_MX32) * I64(R_VY)) + (I64(R_MX33) * I64(R_VZ)));
+
+        int64_t mac1 = gte_clamp_mac(cpu, 1, (((int64_t)R_CV1) << 12) + (I64(R_MX11) * I64(R_VX))); 
+        int64_t mac2 = gte_clamp_mac(cpu, 2, (((int64_t)R_CV2) << 12) + (I64(R_MX21) * I64(R_VX))); 
+        int64_t mac3 = gte_clamp_mac(cpu, 3, (((int64_t)R_CV3) << 12) + (I64(R_MX31) * I64(R_VX))); 
+
+        gte_clamp_ir(cpu, 1, mac1, 0);
+        gte_clamp_ir(cpu, 2, mac2, 0);
+        gte_clamp_ir(cpu, 3, mac3, 0);
+    } else {
+        R_MAC1 = gte_clamp_mac(cpu, 1, (((int64_t)R_CV1) << 12) + (I64(R_MX11) * I64(R_VX)) + (I64(R_MX12) * I64(R_VY)) + (I64(R_MX13) * I64(R_VZ)));
+        R_MAC2 = gte_clamp_mac(cpu, 2, (((int64_t)R_CV2) << 12) + (I64(R_MX21) * I64(R_VX)) + (I64(R_MX22) * I64(R_VY)) + (I64(R_MX23) * I64(R_VZ)));
+        R_MAC3 = gte_clamp_mac(cpu, 3, (((int64_t)R_CV3) << 12) + (I64(R_MX31) * I64(R_VX)) + (I64(R_MX32) * I64(R_VY)) + (I64(R_MX33) * I64(R_VZ)));
+    }
+
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
+}
+
+#undef R_VX
+#undef R_VY
+#undef R_VZ
+#undef R_MX11
+#undef R_MX11
+#undef R_MX12
+#undef R_MX13
+#undef R_MX21
+#undef R_MX22
+#undef R_MX23
+#undef R_MX31
+#undef R_MX32
+#undef R_MX33
+#undef R_CV1
+#undef R_CV2
+#undef R_CV3
+
+// To-do: Fix flags
+void psx_gte_i_ncds(psx_cpu_t* cpu) {
+    R_FLAG = 0;
+
+    int64_t vx = (int64_t)((int16_t)cpu->cop2_dr.v[0].p[0]);
+    int64_t vy = (int64_t)((int16_t)cpu->cop2_dr.v[0].p[1]);
+    int64_t vz = (int64_t)cpu->cop2_dr.v[0].z;
+
+    R_MAC1 = gte_clamp_mac(cpu, 1, (I64(R_L11) * vx) + (I64(R_L12) * vy) + (I64(R_L13) * vz));
+    R_MAC2 = gte_clamp_mac(cpu, 2, (I64(R_L21) * vx) + (I64(R_L22) * vy) + (I64(R_L23) * vz));
+    R_MAC3 = gte_clamp_mac(cpu, 3, (I64(R_L31) * vx) + (I64(R_L32) * vy) + (I64(R_L33) * vz));
+
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
+
+    R_MAC1 = gte_clamp_mac(cpu, 1, (I64(R_RBK) << 12) + (I64(R_LR1) * I64(R_IR1)) + (I64(R_LR2) * I64(R_IR2)) + (I64(R_LR3) * I64(R_IR3)));
+    R_MAC2 = gte_clamp_mac(cpu, 2, (I64(R_GBK) << 12) + (I64(R_LG1) * I64(R_IR1)) + (I64(R_LG2) * I64(R_IR2)) + (I64(R_LG3) * I64(R_IR3)));
+    R_MAC3 = gte_clamp_mac(cpu, 3, (I64(R_BBK) << 12) + (I64(R_LB1) * I64(R_IR1)) + (I64(R_LB2) * I64(R_IR2)) + (I64(R_LB3) * I64(R_IR3)));
+
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
+
+    int64_t ir1 = gte_clamp_ir(cpu, 1, gte_clamp_mac(cpu, 1, ((I64(R_RFC) << 12) - ((I64(R_RC << 4)) * I64(R_IR1)))), 0);
+    int64_t ir2 = gte_clamp_ir(cpu, 2, gte_clamp_mac(cpu, 2, ((I64(R_GFC) << 12) - ((I64(R_GC << 4)) * I64(R_IR2)))), 0);
+    int64_t ir3 = gte_clamp_ir(cpu, 3, gte_clamp_mac(cpu, 3, ((I64(R_BFC) << 12) - ((I64(R_BC << 4)) * I64(R_IR3)))), 0);
+
+    R_MAC1 = gte_clamp_mac(cpu, 1, ((I64(R_RC << 4)) * I64(R_IR1)) + (I64(R_IR0) * ir1));
+    R_MAC2 = gte_clamp_mac(cpu, 2, ((I64(R_GC << 4)) * I64(R_IR2)) + (I64(R_IR0) * ir2));
+    R_MAC3 = gte_clamp_mac(cpu, 3, ((I64(R_BC << 4)) * I64(R_IR3)) + (I64(R_IR0) * ir3));
+
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
+
+    R_RGB0 = R_RGB1;
+    R_RGB1 = R_RGB2;
+    R_CD2 = R_CODE;
+
+    R_RC2 = gte_clamp_rgb(cpu, 1, R_MAC1 >> 4);
+    R_GC2 = gte_clamp_rgb(cpu, 2, R_MAC2 >> 4);
+    R_BC2 = gte_clamp_rgb(cpu, 3, R_MAC3 >> 4);
+}
+
+void psx_gte_i_cdp(psx_cpu_t* cpu) {
+    printf("cdp: Unimplemented GTE instruction\n");
+}
+
+void psx_gte_i_ncdt(psx_cpu_t* cpu) {
+    printf("ncdt: Unimplemented GTE instruction\n");
+}
+
+void psx_gte_i_nccs(psx_cpu_t* cpu) {
+    NCCS(0);
+}
+
+void psx_gte_i_cc(psx_cpu_t* cpu) {
+    printf("cc: Unimplemented GTE instruction\n");
+}
+
+void psx_gte_i_ncs(psx_cpu_t* cpu) {
+    printf("ncs: Unimplemented GTE instruction\n");
+}
+
+void psx_gte_i_nct(psx_cpu_t* cpu) {
+    printf("nct: Unimplemented GTE instruction\n");
+}
+
+void psx_gte_i_sqr(psx_cpu_t* cpu) {
+    R_FLAG = 0;
+
+    R_MAC1 = gte_clamp_mac(cpu, 1, I64(R_IR1) * I64(R_IR1));
+    R_MAC2 = gte_clamp_mac(cpu, 2, I64(R_IR2) * I64(R_IR2));
+    R_MAC3 = gte_clamp_mac(cpu, 3, I64(R_IR3) * I64(R_IR3));
+
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
+}
+
+void psx_gte_i_dcpl(psx_cpu_t* cpu) {
+    printf("dpcl: Unimplemented GTE instruction\n");
+}
+
+void psx_gte_i_dpct(psx_cpu_t* cpu) {
+    DPCT1;
+    DPCT1;
+    DPCT1;
+}
+
+void psx_gte_i_avsz3(psx_cpu_t* cpu) {
+    R_FLAG = 0;
+
+    int64_t avg = I64(R_ZSF3) * (R_SZ1 + R_SZ2 + R_SZ3);
+
+    R_MAC0 = (int)gte_clamp_mac0(cpu, avg);
+    R_OTZ = gte_clamp_sz3(cpu, avg >> 12);
+}
+
+void psx_gte_i_avsz4(psx_cpu_t* cpu) {
+    R_FLAG = 0;
+
+    int64_t avg = I64(R_ZSF4) * (R_SZ0 + R_SZ1 + R_SZ2 + R_SZ3);
+
+    R_MAC0 = (int)gte_clamp_mac0(cpu, avg);
+    R_OTZ = gte_clamp_sz3(cpu, avg >> 12);
+}
+
+void psx_gte_i_rtpt(psx_cpu_t* cpu) {
+    GTE_RTP(0);
+    GTE_RTP(1);
+    GTE_RTP_DQ(2);
+}
+
+void psx_gte_i_gpf(psx_cpu_t* cpu) {
+    R_FLAG = 0;
+
+    R_MAC1 = gte_clamp_mac(cpu, 1, R_IR0 * R_IR1);
+    R_MAC2 = gte_clamp_mac(cpu, 2, R_IR0 * R_IR2);
+    R_MAC3 = gte_clamp_mac(cpu, 3, R_IR0 * R_IR3);
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
+    R_RGB0 = R_RGB1;
+    R_RGB1 = R_RGB2;
+    R_CD2 = R_CODE;
+    R_RC2 = gte_clamp_rgb(cpu, 1, R_MAC1 >> 4);
+    R_GC2 = gte_clamp_rgb(cpu, 2, R_MAC2 >> 4);
+    R_BC2 = gte_clamp_rgb(cpu, 3, R_MAC3 >> 4);
+}
+
+void psx_gte_i_gpl(psx_cpu_t* cpu) {
+    R_FLAG = 0;
+
+    R_MAC1 = gte_clamp_mac(cpu, 1, (I64(R_MAC1) << cpu->gte_sf) + (R_IR0 * R_IR1));
+    R_MAC2 = gte_clamp_mac(cpu, 2, (I64(R_MAC2) << cpu->gte_sf) + (R_IR0 * R_IR2));
+    R_MAC3 = gte_clamp_mac(cpu, 3, (I64(R_MAC3) << cpu->gte_sf) + (R_IR0 * R_IR3));
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
+    R_RGB0 = R_RGB1;
+    R_RGB1 = R_RGB2;
+    R_CD2 = R_CODE;
+    R_RC2 = gte_clamp_rgb(cpu, 1, R_MAC1 >> 4);
+    R_GC2 = gte_clamp_rgb(cpu, 2, R_MAC2 >> 4);
+    R_BC2 = gte_clamp_rgb(cpu, 3, R_MAC3 >> 4);
+}
+
+void psx_gte_i_ncct(psx_cpu_t* cpu) {
+    NCCS(0);
+    NCCS(1);
+    NCCS(2);
+}
+
+#undef R_R0
+#undef R_A0
+#undef R_RA
+
+#undef OP
+#undef S
+#undef T
+#undef D
+#undef IMM5
+#undef CMT
+#undef SOP
+#undef IMM26
+#undef IMM16
+#undef IMM16S
+
+#undef TRACE_M
+#undef TRACE_I16S
+#undef TRACE_I16D
+#undef TRACE_I5D
+#undef TRACE_I26
+#undef TRACE_RT
+#undef TRACE_C0M
+#undef TRACE_C2M
+#undef TRACE_C2MC
+#undef TRACE_B
+#undef TRACE_RS
+#undef TRACE_MTF
+#undef TRACE_RD
+#undef TRACE_MD
+#undef TRACE_I20
+#undef TRACE_N
+
+#undef DO_PENDING_LOAD
+
+#undef DEBUG_ALL
+
+#undef SE8
 #undef SE16
\ No newline at end of file
--- a/psx/cpu.h
+++ b/psx/cpu.h
@@ -1,433 +1,433 @@
-#ifndef CPU_H
-#define CPU_H
-
-#include <stdint.h>
-#include <stdio.h>
-
-#include "bus.h"
-
-#define PSX_CPU_CPS 33868800 // 33868800 Clocks/s
-#define PSX_CPU_FREQ 33.868800f // 33.868800 MHz
-
-struct psx_cpu_t;
-
-typedef struct psx_cpu_t psx_cpu_t;
-
-typedef void (*psx_cpu_kcall_hook_t)(psx_cpu_t*);
-
-/*
-    cop0r0      - N/A
-    cop0r1      - N/A
-    cop0r2      - N/A
-    cop0r3      - BPC - Breakpoint on execute (R/W)
-    cop0r4      - N/A
-    cop0r5      - BDA - Breakpoint on data access (R/W)
-    cop0r6      - JUMPDEST - Randomly memorized jump address (R)
-    cop0r7      - DCIC - Breakpoint control (R/W)
-    cop0r8      - BadVaddr - Bad Virtual Address (R)
-    cop0r9      - BDAM - Data Access breakpoint mask (R/W)
-    cop0r10     - N/A
-    cop0r11     - BPCM - Execute breakpoint mask (R/W)
-    cop0r12     - SR - System status register (R/W)
-    cop0r13     - CAUSE - Describes the most recently recognised exception (R)
-    cop0r14     - EPC - Return Address from Trap (R)
-    cop0r15     - PRID - Processor ID (R)
-*/
-
-#define COP0_BPC      3
-#define COP0_BDA      5
-#define COP0_JUMPDEST 6
-#define COP0_DCIC     7
-#define COP0_BADVADDR 8
-#define COP0_BDAM     9
-#define COP0_BPCM     11
-#define COP0_SR       12
-#define COP0_CAUSE    13
-#define COP0_EPC      14
-#define COP0_PRID     15
-
-/*
-  Name       Alias    Common Usage
-  R0         zero     Constant (always 0)
-  R1         at       Assembler temporary (destroyed by some assembler pseudoinstructions!)
-  R2-R3      v0-v1    Subroutine return values, may be changed by subroutines
-  R4-R7      a0-a3    Subroutine arguments, may be changed by subroutines
-  R8-R15     t0-t7    Temporaries, may be changed by subroutines
-  R16-R23    s0-s7    Static variables, must be saved by subs
-  R24-R25    t8-t9    Temporaries, may be changed by subroutines
-  R26-R27    k0-k1    Reserved for kernel (destroyed by some IRQ handlers!)
-  R28        gp       Global pointer (rarely used)
-  R29        sp       Stack pointer
-  R30        fp(s8)   Frame Pointer, or 9th Static variable, must be saved
-  R31        ra       Return address (used so by JAL,BLTZAL,BGEZAL opcodes)
-  -          pc       Program counter
-  -          hi,lo    Multiply/divide results, may be changed by subroutines
-*/
-
-typedef struct __attribute__((__packed__)) {
-    union {
-        uint32_t xy;
-        int16_t p[2];
-    };
-
-    int16_t z;
-} gte_vertex_t;
-
-typedef struct __attribute__((__packed__)) {
-    union {
-        uint32_t xy;
-        int16_t p[2];
-    };
-} gte_vec2_t;
-
-typedef struct __attribute__((__packed__)) {
-    int32_t x, y, z;
-} gte_vec3_t;
-
-typedef struct __attribute__((__packed__)) {
-    union {
-        uint32_t rgbc;
-        uint8_t c[4];
-    };
-} gte_color_t;
-
-typedef struct __attribute__((__packed__)) {
-    union {
-        uint32_t u32;
-        int16_t c[2];
-    } m[4];
-
-    int16_t m33;
-} gte_matrix_t;
-
-struct __attribute__((__packed__)) psx_cpu_t {
-    uint32_t r[32];
-    uint32_t opcode;
-    uint32_t pc, next_pc, saved_pc;
-    uint32_t hi, lo;
-    uint32_t load_d, load_v;
-    uint32_t last_cycles;
-    uint32_t total_cycles;
-    int branch, delay_slot, branch_taken;
-
-    uint32_t cop0_r[16];
-
-    struct {
-        gte_vertex_t v[3];
-        gte_color_t rgbc;
-        uint16_t otz;
-        int16_t ir[4];
-        gte_vec2_t sxy[4];
-        uint16_t sz[4];
-        gte_color_t rgb[3];
-        uint32_t res1;
-        int32_t mac[4];
-        uint16_t irgb, orgb;
-        int32_t lzcs, lzcr;
-    } cop2_dr;
-
-    struct {
-        gte_matrix_t rt;
-        gte_vec3_t tr;
-        gte_matrix_t l;
-        gte_vec3_t bk;
-        gte_matrix_t lr;
-        gte_vec3_t fc;
-        uint32_t ofx, ofy;
-        uint32_t h;
-        int16_t dqa;
-        int32_t dqb;
-        int16_t zsf3, zsf4;
-        uint32_t flag;
-    } cop2_cr;
-
-    int gte_lm;
-    int gte_sf;
-    int gte_mx;
-    int gte_v;
-    int gte_cv;
-    int64_t s_mac0;
-    int64_t s_mac3;
-
-    psx_bus_t* bus;
-
-    psx_cpu_kcall_hook_t a_function_hook;
-    psx_cpu_kcall_hook_t b_function_hook;
-};
-
-/*
-  0     IEc Current Interrupt Enable  (0=Disable, 1=Enable) ;rfe pops IUp here
-  1     KUc Current Kernel/User Mode  (0=Kernel, 1=User)    ;rfe pops KUp here
-  2     IEp Previous Interrupt Disable                      ;rfe pops IUo here
-  3     KUp Previous Kernel/User Mode                       ;rfe pops KUo here
-  4     IEo Old Interrupt Disable                       ;left unchanged by rfe
-  5     KUo Old Kernel/User Mode                        ;left unchanged by rfe
-  6-7   -   Not used (zero)
-  8-15  Im  8 bit interrupt mask fields. When set the corresponding
-            interrupts are allowed to cause an exception.
-  16    Isc Isolate Cache (0=No, 1=Isolate)
-              When isolated, all load and store operations are targetted
-              to the Data cache, and never the main memory.
-              (Used by PSX Kernel, in combination with Port FFFE0130h)
-  17    Swc Swapped cache mode (0=Normal, 1=Swapped)
-              Instruction cache will act as Data cache and vice versa.
-              Use only with Isc to access & invalidate Instr. cache entries.
-              (Not used by PSX Kernel)
-  18    PZ  When set cache parity bits are written as 0.
-  19    CM  Shows the result of the last load operation with the D-cache
-            isolated. It gets set if the cache really contained data
-            for the addressed memory location.
-  20    PE  Cache parity error (Does not cause exception)
-  21    TS  TLB shutdown. Gets set if a programm address simultaneously
-            matches 2 TLB entries.
-            (initial value on reset allows to detect extended CPU version?)
-  22    BEV Boot exception vectors in RAM/ROM (0=RAM/KSEG0, 1=ROM/KSEG1)
-  23-24 -   Not used (zero)
-  25    RE  Reverse endianness   (0=Normal endianness, 1=Reverse endianness)
-              Reverses the byte order in which data is stored in
-              memory. (lo-hi -> hi-lo)
-              (Affects only user mode, not kernel mode) (?)
-              (The bit doesn't exist in PSX ?)
-  26-27 -   Not used (zero)
-  28    CU0 COP0 Enable (0=Enable only in Kernel Mode, 1=Kernel and User Mode)
-  29    CU1 COP1 Enable (0=Disable, 1=Enable) (none in PSX)
-  30    CU2 COP2 Enable (0=Disable, 1=Enable) (GTE in PSX)
-  31    CU3 COP3 Enable (0=Disable, 1=Enable) (none in PSX)
-*/
-
-#define SR_IEC 0x00000001
-#define SR_KUC 0x00000002
-#define SR_IEP 0x00000004
-#define SR_KUP 0x00000008
-#define SR_IEO 0x00000010
-#define SR_KUO 0x00000020
-#define SR_IM  0x0000ff00
-#define SR_IM0 0x00000100
-#define SR_IM1 0x00000200
-#define SR_IM2 0x00000400
-#define SR_IM3 0x00000800
-#define SR_IM4 0x00001000
-#define SR_IM5 0x00002000
-#define SR_IM6 0x00004000
-#define SR_IM7 0x00008000
-#define SR_ISC 0x00010000
-#define SR_SWC 0x00020000
-#define SR_PZ  0x00040000
-#define SR_CM  0x00080000
-#define SR_PE  0x00100000
-#define SR_TS  0x00200000
-#define SR_BEV 0x00400000
-#define SR_RE  0x02000000
-#define SR_CU0 0x10000000
-#define SR_CU1 0x20000000
-#define SR_CU2 0x40000000
-#define SR_CU3 0x80000000
-
-psx_cpu_t* psx_cpu_create();
-void psx_cpu_init(psx_cpu_t*, psx_bus_t*);
-void psx_cpu_destroy(psx_cpu_t*);
-void psx_cpu_cycle(psx_cpu_t*);
-void psx_cpu_exception(psx_cpu_t*, uint32_t);
-void psx_cpu_set_irq_pending(psx_cpu_t*);
-void psx_cpu_load_state(psx_cpu_t*, FILE*);
-void psx_cpu_save_state(psx_cpu_t*, FILE*);
-void psx_cpu_fetch(psx_cpu_t*);
-void psx_cpu_set_a_kcall_hook(psx_cpu_t*, psx_cpu_kcall_hook_t);
-void psx_cpu_set_b_kcall_hook(psx_cpu_t*, psx_cpu_kcall_hook_t);
-int psx_cpu_check_irq(psx_cpu_t*);
-
-/*
-    00h INT     Interrupt
-    01h MOD     TLB modification (none such in PSX)
-    02h TLBL    TLB load         (none such in PSX)
-    03h TLBS    TLB store        (none such in PSX)
-    04h AdEL    Address error, Data load or Instruction fetch
-    05h AdES    Address error, Data store
-                The address errors occur when attempting to read
-                outside of KUseg in user mode and when the address
-                is misaligned. (See also: BadVaddr register)
-    06h IBE     Bus error on Instruction fetch
-    07h DBE     Bus error on Data load/store
-    08h Syscall Generated unconditionally by syscall instruction
-    09h BP      Breakpoint - break instruction
-    0Ah RI      Reserved instruction
-    0Bh CpU     Coprocessor unusable
-    0Ch Ov      Arithmetic overflow
-*/
-
-#define CAUSE_INT       (0x00 << 2)
-#define CAUSE_MOD       (0x01 << 2)
-#define CAUSE_TLBL      (0x02 << 2)
-#define CAUSE_TLBS      (0x03 << 2)
-#define CAUSE_ADEL      (0x04 << 2)
-#define CAUSE_ADES      (0x05 << 2)
-#define CAUSE_IBE       (0x06 << 2)
-#define CAUSE_DBE       (0x07 << 2)
-#define CAUSE_SYSCALL   (0x08 << 2)
-#define CAUSE_BP        (0x09 << 2)
-#define CAUSE_RI        (0x0a << 2)
-#define CAUSE_CPU       (0x0b << 2)
-#define CAUSE_OV        (0x0c << 2)
-
-/*
-  31   Error Flag (Bit30..23, and 18..13 ORed together) (Read only)
-  30   MAC1 Result positive 44bit overflow (max +7FFFFFFFFFFh) ;\triggered
-  29   MAC2 Result positive 44bit overflow (max +7FFFFFFFFFFh) ; during
-  28   MAC3 Result positive 44bit overflow (max +7FFFFFFFFFFh) ; calculations
-  27   MAC1 Result negative 44bit overflow (min -80000000000h) ;
-  26   MAC2 Result negative 44bit overflow (min -80000000000h) ;
-  25   MAC3 Result negative 44bit overflow (min -80000000000h) ;/
-  24   IR1 saturated to +0000h..+7FFFh (lm=1) or to -8000h..+7FFFh (lm=0)
-  23   IR2 saturated to +0000h..+7FFFh (lm=1) or to -8000h..+7FFFh (lm=0)
-  22   IR3 saturated to +0000h..+7FFFh (lm=1) or to -8000h..+7FFFh (lm=0)
-  21   Color-FIFO-R saturated to +00h..+FFh
-  20   Color-FIFO-G saturated to +00h..+FFh
-  19   Color-FIFO-B saturated to +00h..+FFh
-  18   SZ3 or OTZ saturated to +0000h..+FFFFh
-  17   Divide overflow. RTPS/RTPT division result saturated to max=1FFFFh
-  16   MAC0 Result positive 32bit overflow (max +7FFFFFFFh)    ;\triggered on
-  15   MAC0 Result negative 32bit overflow (min -80000000h)    ;/final result
-  14   SX2 saturated to -0400h..+03FFh
-  13   SY2 saturated to -0400h..+03FFh
-*/
-
-#define GTEF_SY2SAT 0x00002000
-#define GTEF_SX2SAT 0x00004000
-#define GTEF_M0POVF 0x00008000
-#define GTEF_M0NOVF 0x00010000
-#define GTEF_DIVOVF 0x00020000
-#define GTEF_SZ3SAT 0x00040000
-#define GTEF_CFRSAT 0x00080000
-#define GTEF_CFGSAT 0x00100000
-#define GTEF_CFBSAT 0x00200000
-#define GTEF_IR3SAT 0x00400000
-#define GTEF_IR2SAT 0x00800000
-#define GTEF_IR1SAT 0x01000000
-#define GTEF_M3NOVF 0x02000000
-#define GTEF_M2NOVF 0x04000000
-#define GTEF_M1NOVF 0x08000000
-#define GTEF_M3POVF 0x10000000
-#define GTEF_M2POVF 0x20000000
-#define GTEF_M1POVF 0x40000000
-#define GTEF_ERRORF 0x80000000
-
-void psx_cpu_i_invalid(psx_cpu_t*);
-
-// Primary
-void psx_cpu_i_special(psx_cpu_t*);
-void psx_cpu_i_bxx(psx_cpu_t*);
-void psx_cpu_i_j(psx_cpu_t*);
-void psx_cpu_i_jal(psx_cpu_t*);
-void psx_cpu_i_beq(psx_cpu_t*);
-void psx_cpu_i_bne(psx_cpu_t*);
-void psx_cpu_i_blez(psx_cpu_t*);
-void psx_cpu_i_bgtz(psx_cpu_t*);
-void psx_cpu_i_addi(psx_cpu_t*);
-void psx_cpu_i_addiu(psx_cpu_t*);
-void psx_cpu_i_slti(psx_cpu_t*);
-void psx_cpu_i_sltiu(psx_cpu_t*);
-void psx_cpu_i_andi(psx_cpu_t*);
-void psx_cpu_i_ori(psx_cpu_t*);
-void psx_cpu_i_xori(psx_cpu_t*);
-void psx_cpu_i_lui(psx_cpu_t*);
-void psx_cpu_i_cop0(psx_cpu_t*);
-void psx_cpu_i_cop1(psx_cpu_t*);
-void psx_cpu_i_cop2(psx_cpu_t*);
-void psx_cpu_i_cop3(psx_cpu_t*);
-void psx_cpu_i_lb(psx_cpu_t*);
-void psx_cpu_i_lh(psx_cpu_t*);
-void psx_cpu_i_lwl(psx_cpu_t*);
-void psx_cpu_i_lw(psx_cpu_t*);
-void psx_cpu_i_lbu(psx_cpu_t*);
-void psx_cpu_i_lhu(psx_cpu_t*);
-void psx_cpu_i_lwr(psx_cpu_t*);
-void psx_cpu_i_sb(psx_cpu_t*);
-void psx_cpu_i_sh(psx_cpu_t*);
-void psx_cpu_i_swl(psx_cpu_t*);
-void psx_cpu_i_sw(psx_cpu_t*);
-void psx_cpu_i_swr(psx_cpu_t*);
-void psx_cpu_i_lwc0(psx_cpu_t*);
-void psx_cpu_i_lwc1(psx_cpu_t*);
-void psx_cpu_i_lwc2(psx_cpu_t*);
-void psx_cpu_i_lwc3(psx_cpu_t*);
-void psx_cpu_i_swc0(psx_cpu_t*);
-void psx_cpu_i_swc1(psx_cpu_t*);
-void psx_cpu_i_swc2(psx_cpu_t*);
-void psx_cpu_i_swc3(psx_cpu_t*);
-
-// Secondary
-void psx_cpu_i_sll(psx_cpu_t*);
-void psx_cpu_i_srl(psx_cpu_t*);
-void psx_cpu_i_sra(psx_cpu_t*);
-void psx_cpu_i_sllv(psx_cpu_t*);
-void psx_cpu_i_srlv(psx_cpu_t*);
-void psx_cpu_i_srav(psx_cpu_t*);
-void psx_cpu_i_jr(psx_cpu_t*);
-void psx_cpu_i_jalr(psx_cpu_t*);
-void psx_cpu_i_syscall(psx_cpu_t*);
-void psx_cpu_i_break(psx_cpu_t*);
-void psx_cpu_i_mfhi(psx_cpu_t*);
-void psx_cpu_i_mthi(psx_cpu_t*);
-void psx_cpu_i_mflo(psx_cpu_t*);
-void psx_cpu_i_mtlo(psx_cpu_t*);
-void psx_cpu_i_mult(psx_cpu_t*);
-void psx_cpu_i_multu(psx_cpu_t*);
-void psx_cpu_i_div(psx_cpu_t*);
-void psx_cpu_i_divu(psx_cpu_t*);
-void psx_cpu_i_add(psx_cpu_t*);
-void psx_cpu_i_addu(psx_cpu_t*);
-void psx_cpu_i_sub(psx_cpu_t*);
-void psx_cpu_i_subu(psx_cpu_t*);
-void psx_cpu_i_and(psx_cpu_t*);
-void psx_cpu_i_or(psx_cpu_t*);
-void psx_cpu_i_xor(psx_cpu_t*);
-void psx_cpu_i_nor(psx_cpu_t*);
-void psx_cpu_i_slt(psx_cpu_t*);
-void psx_cpu_i_sltu(psx_cpu_t*);
-
-// COP0
-void psx_cpu_i_mfc0(psx_cpu_t*);
-void psx_cpu_i_mtc0(psx_cpu_t*);
-void psx_cpu_i_rfe(psx_cpu_t*);
-
-// BXX
-void psx_cpu_i_bltz(psx_cpu_t*);
-void psx_cpu_i_bgez(psx_cpu_t*);
-void psx_cpu_i_bltzal(psx_cpu_t*);
-void psx_cpu_i_bgezal(psx_cpu_t*);
-
-// COP2
-void psx_cpu_i_mfc2(psx_cpu_t*);
-void psx_cpu_i_cfc2(psx_cpu_t*);
-void psx_cpu_i_mtc2(psx_cpu_t*);
-void psx_cpu_i_ctc2(psx_cpu_t*);
-void psx_cpu_i_gte(psx_cpu_t*);
-
-// GTE instructions
-void psx_gte_i_invalid(psx_cpu_t*);
-void psx_gte_i_rtps(psx_cpu_t*);
-void psx_gte_i_nclip(psx_cpu_t*);
-void psx_gte_i_op(psx_cpu_t*);
-void psx_gte_i_dpcs(psx_cpu_t*);
-void psx_gte_i_intpl(psx_cpu_t*);
-void psx_gte_i_mvmva(psx_cpu_t*);
-void psx_gte_i_ncds(psx_cpu_t*);
-void psx_gte_i_cdp(psx_cpu_t*);
-void psx_gte_i_ncdt(psx_cpu_t*);
-void psx_gte_i_nccs(psx_cpu_t*);
-void psx_gte_i_cc(psx_cpu_t*);
-void psx_gte_i_ncs(psx_cpu_t*);
-void psx_gte_i_nct(psx_cpu_t*);
-void psx_gte_i_sqr(psx_cpu_t*);
-void psx_gte_i_dcpl(psx_cpu_t*);
-void psx_gte_i_dpct(psx_cpu_t*);
-void psx_gte_i_avsz3(psx_cpu_t*);
-void psx_gte_i_avsz4(psx_cpu_t*);
-void psx_gte_i_rtpt(psx_cpu_t*);
-void psx_gte_i_gpf(psx_cpu_t*);
-void psx_gte_i_gpl(psx_cpu_t*);
-void psx_gte_i_ncct(psx_cpu_t*);
-
-typedef void (*psx_cpu_instruction_t)(psx_cpu_t*);
-
+#ifndef CPU_H
+#define CPU_H
+
+#include <stdint.h>
+#include <stdio.h>
+
+#include "bus.h"
+
+#define PSX_CPU_CPS 33868800 // 33868800 Clocks/s
+#define PSX_CPU_FREQ 33.868800f // 33.868800 MHz
+
+struct psx_cpu_t;
+
+typedef struct psx_cpu_t psx_cpu_t;
+
+typedef void (*psx_cpu_kcall_hook_t)(psx_cpu_t*);
+
+/*
+    cop0r0      - N/A
+    cop0r1      - N/A
+    cop0r2      - N/A
+    cop0r3      - BPC - Breakpoint on execute (R/W)
+    cop0r4      - N/A
+    cop0r5      - BDA - Breakpoint on data access (R/W)
+    cop0r6      - JUMPDEST - Randomly memorized jump address (R)
+    cop0r7      - DCIC - Breakpoint control (R/W)
+    cop0r8      - BadVaddr - Bad Virtual Address (R)
+    cop0r9      - BDAM - Data Access breakpoint mask (R/W)
+    cop0r10     - N/A
+    cop0r11     - BPCM - Execute breakpoint mask (R/W)
+    cop0r12     - SR - System status register (R/W)
+    cop0r13     - CAUSE - Describes the most recently recognised exception (R)
+    cop0r14     - EPC - Return Address from Trap (R)
+    cop0r15     - PRID - Processor ID (R)
+*/
+
+#define COP0_BPC      3
+#define COP0_BDA      5
+#define COP0_JUMPDEST 6
+#define COP0_DCIC     7
+#define COP0_BADVADDR 8
+#define COP0_BDAM     9
+#define COP0_BPCM     11
+#define COP0_SR       12
+#define COP0_CAUSE    13
+#define COP0_EPC      14
+#define COP0_PRID     15
+
+/*
+  Name       Alias    Common Usage
+  R0         zero     Constant (always 0)
+  R1         at       Assembler temporary (destroyed by some assembler pseudoinstructions!)
+  R2-R3      v0-v1    Subroutine return values, may be changed by subroutines
+  R4-R7      a0-a3    Subroutine arguments, may be changed by subroutines
+  R8-R15     t0-t7    Temporaries, may be changed by subroutines
+  R16-R23    s0-s7    Static variables, must be saved by subs
+  R24-R25    t8-t9    Temporaries, may be changed by subroutines
+  R26-R27    k0-k1    Reserved for kernel (destroyed by some IRQ handlers!)
+  R28        gp       Global pointer (rarely used)
+  R29        sp       Stack pointer
+  R30        fp(s8)   Frame Pointer, or 9th Static variable, must be saved
+  R31        ra       Return address (used so by JAL,BLTZAL,BGEZAL opcodes)
+  -          pc       Program counter
+  -          hi,lo    Multiply/divide results, may be changed by subroutines
+*/
+
+typedef struct __attribute__((__packed__)) {
+    union {
+        uint32_t xy;
+        int16_t p[2];
+    };
+
+    int16_t z;
+} gte_vertex_t;
+
+typedef struct __attribute__((__packed__)) {
+    union {
+        uint32_t xy;
+        int16_t p[2];
+    };
+} gte_vec2_t;
+
+typedef struct __attribute__((__packed__)) {
+    int32_t x, y, z;
+} gte_vec3_t;
+
+typedef struct __attribute__((__packed__)) {
+    union {
+        uint32_t rgbc;
+        uint8_t c[4];
+    };
+} gte_color_t;
+
+typedef struct __attribute__((__packed__)) {
+    union {
+        uint32_t u32;
+        int16_t c[2];
+    } m[4];
+
+    int16_t m33;
+} gte_matrix_t;
+
+struct __attribute__((__packed__)) psx_cpu_t {
+    uint32_t r[32];
+    uint32_t opcode;
+    uint32_t pc, next_pc, saved_pc;
+    uint32_t hi, lo;
+    uint32_t load_d, load_v;
+    uint32_t last_cycles;
+    uint32_t total_cycles;
+    int branch, delay_slot, branch_taken;
+
+    uint32_t cop0_r[16];
+
+    struct {
+        gte_vertex_t v[3];
+        gte_color_t rgbc;
+        uint16_t otz;
+        int16_t ir[4];
+        gte_vec2_t sxy[4];
+        uint16_t sz[4];
+        gte_color_t rgb[3];
+        uint32_t res1;
+        int32_t mac[4];
+        uint16_t irgb, orgb;
+        int32_t lzcs, lzcr;
+    } cop2_dr;
+
+    struct {
+        gte_matrix_t rt;
+        gte_vec3_t tr;
+        gte_matrix_t l;
+        gte_vec3_t bk;
+        gte_matrix_t lr;
+        gte_vec3_t fc;
+        uint32_t ofx, ofy;
+        uint32_t h;
+        int16_t dqa;
+        int32_t dqb;
+        int16_t zsf3, zsf4;
+        uint32_t flag;
+    } cop2_cr;
+
+    int gte_lm;
+    int gte_sf;
+    int gte_mx;
+    int gte_v;
+    int gte_cv;
+    int64_t s_mac0;
+    int64_t s_mac3;
+
+    psx_bus_t* bus;
+
+    psx_cpu_kcall_hook_t a_function_hook;
+    psx_cpu_kcall_hook_t b_function_hook;
+};
+
+/*
+  0     IEc Current Interrupt Enable  (0=Disable, 1=Enable) ;rfe pops IUp here
+  1     KUc Current Kernel/User Mode  (0=Kernel, 1=User)    ;rfe pops KUp here
+  2     IEp Previous Interrupt Disable                      ;rfe pops IUo here
+  3     KUp Previous Kernel/User Mode                       ;rfe pops KUo here
+  4     IEo Old Interrupt Disable                       ;left unchanged by rfe
+  5     KUo Old Kernel/User Mode                        ;left unchanged by rfe
+  6-7   -   Not used (zero)
+  8-15  Im  8 bit interrupt mask fields. When set the corresponding
+            interrupts are allowed to cause an exception.
+  16    Isc Isolate Cache (0=No, 1=Isolate)
+              When isolated, all load and store operations are targetted
+              to the Data cache, and never the main memory.
+              (Used by PSX Kernel, in combination with Port FFFE0130h)
+  17    Swc Swapped cache mode (0=Normal, 1=Swapped)
+              Instruction cache will act as Data cache and vice versa.
+              Use only with Isc to access & invalidate Instr. cache entries.
+              (Not used by PSX Kernel)
+  18    PZ  When set cache parity bits are written as 0.
+  19    CM  Shows the result of the last load operation with the D-cache
+            isolated. It gets set if the cache really contained data
+            for the addressed memory location.
+  20    PE  Cache parity error (Does not cause exception)
+  21    TS  TLB shutdown. Gets set if a programm address simultaneously
+            matches 2 TLB entries.
+            (initial value on reset allows to detect extended CPU version?)
+  22    BEV Boot exception vectors in RAM/ROM (0=RAM/KSEG0, 1=ROM/KSEG1)
+  23-24 -   Not used (zero)
+  25    RE  Reverse endianness   (0=Normal endianness, 1=Reverse endianness)
+              Reverses the byte order in which data is stored in
+              memory. (lo-hi -> hi-lo)
+              (Affects only user mode, not kernel mode) (?)
+              (The bit doesn't exist in PSX ?)
+  26-27 -   Not used (zero)
+  28    CU0 COP0 Enable (0=Enable only in Kernel Mode, 1=Kernel and User Mode)
+  29    CU1 COP1 Enable (0=Disable, 1=Enable) (none in PSX)
+  30    CU2 COP2 Enable (0=Disable, 1=Enable) (GTE in PSX)
+  31    CU3 COP3 Enable (0=Disable, 1=Enable) (none in PSX)
+*/
+
+#define SR_IEC 0x00000001
+#define SR_KUC 0x00000002
+#define SR_IEP 0x00000004
+#define SR_KUP 0x00000008
+#define SR_IEO 0x00000010
+#define SR_KUO 0x00000020
+#define SR_IM  0x0000ff00
+#define SR_IM0 0x00000100
+#define SR_IM1 0x00000200
+#define SR_IM2 0x00000400
+#define SR_IM3 0x00000800
+#define SR_IM4 0x00001000
+#define SR_IM5 0x00002000
+#define SR_IM6 0x00004000
+#define SR_IM7 0x00008000
+#define SR_ISC 0x00010000
+#define SR_SWC 0x00020000
+#define SR_PZ  0x00040000
+#define SR_CM  0x00080000
+#define SR_PE  0x00100000
+#define SR_TS  0x00200000
+#define SR_BEV 0x00400000
+#define SR_RE  0x02000000
+#define SR_CU0 0x10000000
+#define SR_CU1 0x20000000
+#define SR_CU2 0x40000000
+#define SR_CU3 0x80000000
+
+psx_cpu_t* psx_cpu_create();
+void psx_cpu_init(psx_cpu_t*, psx_bus_t*);
+void psx_cpu_destroy(psx_cpu_t*);
+void psx_cpu_cycle(psx_cpu_t*);
+void psx_cpu_exception(psx_cpu_t*, uint32_t);
+void psx_cpu_set_irq_pending(psx_cpu_t*);
+void psx_cpu_load_state(psx_cpu_t*, FILE*);
+void psx_cpu_save_state(psx_cpu_t*, FILE*);
+void psx_cpu_fetch(psx_cpu_t*);
+void psx_cpu_set_a_kcall_hook(psx_cpu_t*, psx_cpu_kcall_hook_t);
+void psx_cpu_set_b_kcall_hook(psx_cpu_t*, psx_cpu_kcall_hook_t);
+int psx_cpu_check_irq(psx_cpu_t*);
+
+/*
+    00h INT     Interrupt
+    01h MOD     TLB modification (none such in PSX)
+    02h TLBL    TLB load         (none such in PSX)
+    03h TLBS    TLB store        (none such in PSX)
+    04h AdEL    Address error, Data load or Instruction fetch
+    05h AdES    Address error, Data store
+                The address errors occur when attempting to read
+                outside of KUseg in user mode and when the address
+                is misaligned. (See also: BadVaddr register)
+    06h IBE     Bus error on Instruction fetch
+    07h DBE     Bus error on Data load/store
+    08h Syscall Generated unconditionally by syscall instruction
+    09h BP      Breakpoint - break instruction
+    0Ah RI      Reserved instruction
+    0Bh CpU     Coprocessor unusable
+    0Ch Ov      Arithmetic overflow
+*/
+
+#define CAUSE_INT       (0x00 << 2)
+#define CAUSE_MOD       (0x01 << 2)
+#define CAUSE_TLBL      (0x02 << 2)
+#define CAUSE_TLBS      (0x03 << 2)
+#define CAUSE_ADEL      (0x04 << 2)
+#define CAUSE_ADES      (0x05 << 2)
+#define CAUSE_IBE       (0x06 << 2)
+#define CAUSE_DBE       (0x07 << 2)
+#define CAUSE_SYSCALL   (0x08 << 2)
+#define CAUSE_BP        (0x09 << 2)
+#define CAUSE_RI        (0x0a << 2)
+#define CAUSE_CPU       (0x0b << 2)
+#define CAUSE_OV        (0x0c << 2)
+
+/*
+  31   Error Flag (Bit30..23, and 18..13 ORed together) (Read only)
+  30   MAC1 Result positive 44bit overflow (max +7FFFFFFFFFFh) ;\triggered
+  29   MAC2 Result positive 44bit overflow (max +7FFFFFFFFFFh) ; during
+  28   MAC3 Result positive 44bit overflow (max +7FFFFFFFFFFh) ; calculations
+  27   MAC1 Result negative 44bit overflow (min -80000000000h) ;
+  26   MAC2 Result negative 44bit overflow (min -80000000000h) ;
+  25   MAC3 Result negative 44bit overflow (min -80000000000h) ;/
+  24   IR1 saturated to +0000h..+7FFFh (lm=1) or to -8000h..+7FFFh (lm=0)
+  23   IR2 saturated to +0000h..+7FFFh (lm=1) or to -8000h..+7FFFh (lm=0)
+  22   IR3 saturated to +0000h..+7FFFh (lm=1) or to -8000h..+7FFFh (lm=0)
+  21   Color-FIFO-R saturated to +00h..+FFh
+  20   Color-FIFO-G saturated to +00h..+FFh
+  19   Color-FIFO-B saturated to +00h..+FFh
+  18   SZ3 or OTZ saturated to +0000h..+FFFFh
+  17   Divide overflow. RTPS/RTPT division result saturated to max=1FFFFh
+  16   MAC0 Result positive 32bit overflow (max +7FFFFFFFh)    ;\triggered on
+  15   MAC0 Result negative 32bit overflow (min -80000000h)    ;/final result
+  14   SX2 saturated to -0400h..+03FFh
+  13   SY2 saturated to -0400h..+03FFh
+*/
+
+#define GTEF_SY2SAT 0x00002000
+#define GTEF_SX2SAT 0x00004000
+#define GTEF_M0POVF 0x00008000
+#define GTEF_M0NOVF 0x00010000
+#define GTEF_DIVOVF 0x00020000
+#define GTEF_SZ3SAT 0x00040000
+#define GTEF_CFRSAT 0x00080000
+#define GTEF_CFGSAT 0x00100000
+#define GTEF_CFBSAT 0x00200000
+#define GTEF_IR3SAT 0x00400000
+#define GTEF_IR2SAT 0x00800000
+#define GTEF_IR1SAT 0x01000000
+#define GTEF_M3NOVF 0x02000000
+#define GTEF_M2NOVF 0x04000000
+#define GTEF_M1NOVF 0x08000000
+#define GTEF_M3POVF 0x10000000
+#define GTEF_M2POVF 0x20000000
+#define GTEF_M1POVF 0x40000000
+#define GTEF_ERRORF 0x80000000
+
+void psx_cpu_i_invalid(psx_cpu_t*);
+
+// Primary
+void psx_cpu_i_special(psx_cpu_t*);
+void psx_cpu_i_bxx(psx_cpu_t*);
+void psx_cpu_i_j(psx_cpu_t*);
+void psx_cpu_i_jal(psx_cpu_t*);
+void psx_cpu_i_beq(psx_cpu_t*);
+void psx_cpu_i_bne(psx_cpu_t*);
+void psx_cpu_i_blez(psx_cpu_t*);
+void psx_cpu_i_bgtz(psx_cpu_t*);
+void psx_cpu_i_addi(psx_cpu_t*);
+void psx_cpu_i_addiu(psx_cpu_t*);
+void psx_cpu_i_slti(psx_cpu_t*);
+void psx_cpu_i_sltiu(psx_cpu_t*);
+void psx_cpu_i_andi(psx_cpu_t*);
+void psx_cpu_i_ori(psx_cpu_t*);
+void psx_cpu_i_xori(psx_cpu_t*);
+void psx_cpu_i_lui(psx_cpu_t*);
+void psx_cpu_i_cop0(psx_cpu_t*);
+void psx_cpu_i_cop1(psx_cpu_t*);
+void psx_cpu_i_cop2(psx_cpu_t*);
+void psx_cpu_i_cop3(psx_cpu_t*);
+void psx_cpu_i_lb(psx_cpu_t*);
+void psx_cpu_i_lh(psx_cpu_t*);
+void psx_cpu_i_lwl(psx_cpu_t*);
+void psx_cpu_i_lw(psx_cpu_t*);
+void psx_cpu_i_lbu(psx_cpu_t*);
+void psx_cpu_i_lhu(psx_cpu_t*);
+void psx_cpu_i_lwr(psx_cpu_t*);
+void psx_cpu_i_sb(psx_cpu_t*);
+void psx_cpu_i_sh(psx_cpu_t*);
+void psx_cpu_i_swl(psx_cpu_t*);
+void psx_cpu_i_sw(psx_cpu_t*);
+void psx_cpu_i_swr(psx_cpu_t*);
+void psx_cpu_i_lwc0(psx_cpu_t*);
+void psx_cpu_i_lwc1(psx_cpu_t*);
+void psx_cpu_i_lwc2(psx_cpu_t*);
+void psx_cpu_i_lwc3(psx_cpu_t*);
+void psx_cpu_i_swc0(psx_cpu_t*);
+void psx_cpu_i_swc1(psx_cpu_t*);
+void psx_cpu_i_swc2(psx_cpu_t*);
+void psx_cpu_i_swc3(psx_cpu_t*);
+
+// Secondary
+void psx_cpu_i_sll(psx_cpu_t*);
+void psx_cpu_i_srl(psx_cpu_t*);
+void psx_cpu_i_sra(psx_cpu_t*);
+void psx_cpu_i_sllv(psx_cpu_t*);
+void psx_cpu_i_srlv(psx_cpu_t*);
+void psx_cpu_i_srav(psx_cpu_t*);
+void psx_cpu_i_jr(psx_cpu_t*);
+void psx_cpu_i_jalr(psx_cpu_t*);
+void psx_cpu_i_syscall(psx_cpu_t*);
+void psx_cpu_i_break(psx_cpu_t*);
+void psx_cpu_i_mfhi(psx_cpu_t*);
+void psx_cpu_i_mthi(psx_cpu_t*);
+void psx_cpu_i_mflo(psx_cpu_t*);
+void psx_cpu_i_mtlo(psx_cpu_t*);
+void psx_cpu_i_mult(psx_cpu_t*);
+void psx_cpu_i_multu(psx_cpu_t*);
+void psx_cpu_i_div(psx_cpu_t*);
+void psx_cpu_i_divu(psx_cpu_t*);
+void psx_cpu_i_add(psx_cpu_t*);
+void psx_cpu_i_addu(psx_cpu_t*);
+void psx_cpu_i_sub(psx_cpu_t*);
+void psx_cpu_i_subu(psx_cpu_t*);
+void psx_cpu_i_and(psx_cpu_t*);
+void psx_cpu_i_or(psx_cpu_t*);
+void psx_cpu_i_xor(psx_cpu_t*);
+void psx_cpu_i_nor(psx_cpu_t*);
+void psx_cpu_i_slt(psx_cpu_t*);
+void psx_cpu_i_sltu(psx_cpu_t*);
+
+// COP0
+void psx_cpu_i_mfc0(psx_cpu_t*);
+void psx_cpu_i_mtc0(psx_cpu_t*);
+void psx_cpu_i_rfe(psx_cpu_t*);
+
+// BXX
+void psx_cpu_i_bltz(psx_cpu_t*);
+void psx_cpu_i_bgez(psx_cpu_t*);
+void psx_cpu_i_bltzal(psx_cpu_t*);
+void psx_cpu_i_bgezal(psx_cpu_t*);
+
+// COP2
+void psx_cpu_i_mfc2(psx_cpu_t*);
+void psx_cpu_i_cfc2(psx_cpu_t*);
+void psx_cpu_i_mtc2(psx_cpu_t*);
+void psx_cpu_i_ctc2(psx_cpu_t*);
+void psx_cpu_i_gte(psx_cpu_t*);
+
+// GTE instructions
+void psx_gte_i_invalid(psx_cpu_t*);
+void psx_gte_i_rtps(psx_cpu_t*);
+void psx_gte_i_nclip(psx_cpu_t*);
+void psx_gte_i_op(psx_cpu_t*);
+void psx_gte_i_dpcs(psx_cpu_t*);
+void psx_gte_i_intpl(psx_cpu_t*);
+void psx_gte_i_mvmva(psx_cpu_t*);
+void psx_gte_i_ncds(psx_cpu_t*);
+void psx_gte_i_cdp(psx_cpu_t*);
+void psx_gte_i_ncdt(psx_cpu_t*);
+void psx_gte_i_nccs(psx_cpu_t*);
+void psx_gte_i_cc(psx_cpu_t*);
+void psx_gte_i_ncs(psx_cpu_t*);
+void psx_gte_i_nct(psx_cpu_t*);
+void psx_gte_i_sqr(psx_cpu_t*);
+void psx_gte_i_dcpl(psx_cpu_t*);
+void psx_gte_i_dpct(psx_cpu_t*);
+void psx_gte_i_avsz3(psx_cpu_t*);
+void psx_gte_i_avsz4(psx_cpu_t*);
+void psx_gte_i_rtpt(psx_cpu_t*);
+void psx_gte_i_gpf(psx_cpu_t*);
+void psx_gte_i_gpl(psx_cpu_t*);
+void psx_gte_i_ncct(psx_cpu_t*);
+
+typedef void (*psx_cpu_instruction_t)(psx_cpu_t*);
+
 #endif
\ No newline at end of file
--- a/psx/cpu_debug.h
+++ b/psx/cpu_debug.h
@@ -1,428 +1,428 @@
-#ifdef CPU_TRACE
-
-static const char* g_psx_cpu_a_kcall_symtable[] = {
-    "open(filename=%08x,accessmode=%08x)",
-    "lseek(fd=%08x,offset=%08x,seektype=%08x)",
-    "read(fd=%08x,dst=%08x,length=%08x)",
-    "write(fd=%08x,src=%08x,length=%08x)",
-    "close(fd=%08x)",
-    "ioctl(fd=%08x,cmd=%08x,arg=%08x)",
-    "exit(exitcode=%08x)",
-    "isatty(fd=%08x)",
-    "getc(fd=%08x)",
-    "putc(char=%08x,fd=%08x)",
-    "todigit(char=%08x)",
-    "atof(src=%08x)",
-    "strtoul(src=%08x,src_end=%08x,base=%08x)",
-    "strtol(src=%08x,src_end=%08x,base=%08x)",
-    "abs(val=%08x)",
-    "labs(val=%08x)",
-    "atoi(src=%08x)",
-    "atol(src=%08x)",
-    "atob(src=%08x,num_dst=%08x)",
-    "setjmp(buf=%08x)",
-    "longjmp(buf=%08x,param=%08x)",
-    "strcat(dst=%08x,src=%08x)",
-    "strncat(dst=%08x,src=%08x,maxlen=%08x)",
-    "strcmp(str1=%08x,str2=%08x)",
-    "strncmp(str1=%08x,str2=%08x,maxlen=%08x)",
-    "strcpy(dst=%08x,src=%08x)",
-    "strncpy(dst=%08x,src=%08x,maxlen=%08x)",
-    "strlen(src=%08x)",
-    "index(src=%08x,char=%08x)",
-    "rindex(src=%08x,char=%08x)",
-    "strchr(src=%08x,char=%08x)",
-    "strrchr(src=%08x,char=%08x)",
-    "strpbrk(src=%08x,list=%08x)",
-    "strspn(src=%08x,list=%08x)",
-    "strcspn(src=%08x,list=%08x)",
-    "strtok(src=%08x,list=%08x)",
-    "strstr(str=%08x,substr=%08x)",
-    "toupper(char=%08x)",
-    "tolower(char=%08x)",
-    "bcopy(src=%08x,dst=%08x,len=%08x)",
-    "bzero(dst=%08x,len=%08x)",
-    "bcmp(ptr1=%08x,ptr2=%08x,len=%08x)",
-    "memcpy(dst=%08x,src=%08x,len=%08x)",
-    "memset(dst=%08x,fillbyte=%08x,len=%08x)",
-    "memmove(dst=%08x,src=%08x,len=%08x)",
-    "memcmp(src1=%08x,src2=%08x,len=%08x)",
-    "memchr(src=%08x,scanbyte=%08x,len=%08x)",
-    "rand()",
-    "srand(seed=%08x)",
-    "qsort(base=%08x,nel=%08x,width=%08x,callback=%08x)",
-    "strtod(src=%08x,src_end=%08x)",
-    "malloc(size=%08x)",
-    "free(buf=%08x)",
-    "lsearch(key=%08x,base=%08x,nel=%08x,width=%08x,callback=%08x)",
-    "bsearch(key=%08x,base=%08x,nel=%08x,width=%08x,callback=%08x)",
-    "calloc(sizx=%08x,sizy=%08x)",
-    "realloc(old_buf=%08x,new_siz=%08x)",
-    "InitHeap(addr=%08x,size=%08x)",
-    "_exit(exitcode=%08x)",
-    "getchar()",
-    "putchar(char=%08x)",
-    "gets(dst=%08x)",
-    "puts(src=%08x)",
-    "printf(txt=%08x,param1=%08x,param2=%08x,etc.=%08x)",
-    "SystemErrorUnresolvedException()",
-    "LoadTest(filename=%08x,headerbuf=%08x)",
-    "Load(filename=%08x,headerbuf=%08x)",
-    "Exec(headerbuf=%08x,param1=%08x,param2=%08x)",
-    "FlushCache()",
-    "init_a0_b0_c0_vectors()",
-    "GPU_dw(Xdst=%08x,Ydst=%08x,Xsiz=%08x,Ysiz=%08x,src=%08x)",
-    "gpu_send_dma(Xdst=%08x,Ydst=%08x,Xsiz=%08x,Ysiz=%08x,src=%08x)",
-    "SendGP1Command(gp1cmd=%08x)",
-    "GPU_cw(gp0cmd=%08x)",
-    "GPU_cwp(src=%08x,num=%08x)",
-    "send_gpu_linked_list(src=%08x)",
-    "gpu_abort_dma()",
-    "GetGPUStatus()",
-    "gpu_sync()",
-    "SystemError()",
-    "SystemError()",
-    "LoadExec(filename=%08x,stackbase=%08x,stackoffset=%08x)",
-    "GetSysSp()",
-    "SystemError()",
-    "_96_init()",
-    "_bu_init()",
-    "_96_remove()",
-    "return 0",
-    "return 0",
-    "return 0",
-    "return 0",
-    "dev_tty_init()",
-    "dev_tty_open(fcb=%08x, (unused)path=%08x,accessmode=%08x)",
-    "dev_tty_in_out(fcb=%08x,cmd=%08x)",
-    "dev_tty_ioctl(fcb=%08x,cmd=%08x,arg=%08x)",
-    "dev_cd_open(fcb=%08x,path=%08x,accessmode=%08x)",
-    "dev_cd_read(fcb=%08x,dst=%08x,len=%08x)",
-    "dev_cd_close(fcb=%08x)",
-    "dev_cd_firstfile(fcb=%08x,path=%08x,direntry=%08x)",
-    "dev_cd_nextfile(fcb=%08x,direntry=%08x)",
-    "dev_cd_chdir(fcb=%08x,path=%08x)",
-    "dev_card_open(fcb=%08x,path=%08x,accessmode=%08x)",
-    "dev_card_read(fcb=%08x,dst=%08x,len=%08x)",
-    "dev_card_write(fcb=%08x,src=%08x,len=%08x)",
-    "dev_card_close(fcb=%08x)",
-    "dev_card_firstfile(fcb=%08x,path=%08x,direntry=%08x)",
-    "dev_card_nextfile(fcb=%08x,direntry=%08x)",
-    "dev_card_erase(fcb=%08x,path=%08x)",
-    "dev_card_undelete(fcb=%08x,path=%08x)",
-    "dev_card_format(fcb=%08x)",
-    "dev_card_rename(fcb1=%08x,path=%08x)",
-    "card_clear_error(fcb=%08x) (?)",
-    "_bu_init()",
-    "_96_init()",
-    "_96_remove()",
-    "return 0",
-    "return 0",
-    "return 0",
-    "return 0",
-    "return 0",
-    "CdAsyncSeekL(src=%08x)",
-    "return 0",
-    "return 0",
-    "return 0",
-    "CdAsyncGetStatus(dst=%08x)",
-    "return 0",
-    "CdAsyncReadSector(count=%08x,dst=%08x,mode=%08x)",
-    "return 0",
-    "return 0",
-    "CdAsyncSetMode(mode=%08x)",
-    "return 0",
-    "return 0",
-    "return 0",
-    "return 0",
-    "return 0",
-    "return 0",
-    "return 0",
-    "return 0",
-    "return 0",
-    "return 0",
-    "return 0",
-    "return 0",
-    "return 0",
-    "return 0",
-    "CdromIoIrqFunc1()",
-    "CdromDmaIrqFunc1()",
-    "CdromIoIrqFunc2()",
-    "CdromDmaIrqFunc2()",
-    "CdromGetInt5errCode(dst1=%08x,dst2=%08x)",
-    "CdInitSubFunc()",
-    "AddCDROMDevice()",
-    "AddMemCardDevice()",
-    "AddDuartTtyDevice()",
-    "add_nullcon_driver()",
-    "SystemError()",
-    "SystemError()",
-    "SetConf(num_EvCB=%08x,num_TCB=%08x,stacktop=%08x)",
-    "GetConf(num_EvCB_dst=%08x,num_TCB_dst=%08x,stacktop_dst=%08x)",
-    "SetCdromIrqAutoAbort(type=%08x,flag=%08x)",
-    "SetMem(megabytes=%08x)",
-    "_boot()",
-    "SystemError(type=%08x,errorcode=%08x)",
-    "EnqueueCdIntr()",
-    "DequeueCdIntr()",
-    "CdGetLbn(filename=%08x)",
-    "CdReadSector(count=%08x,sector=%08x,buffer=%08x)",
-    "CdGetStatus()",
-    "bufs_cb_0()",
-    "bufs_cb_1()",
-    "bufs_cb_2()",
-    "bufs_cb_3()",
-    "_card_info(port=%08x)",
-    "_card_load(port=%08x)",
-    "_card_auto(flag=%08x)",
-    "bufs_cb_4()",
-    "card_write_test(port=%08x)",
-    "return 0",
-    "return 0",
-    "ioabort_raw(param=%08x)",
-    "return 0",
-    "GetSystemInfo(index=%08x)"
-};
-
-static const char* g_psx_cpu_b_kcall_symtable[] = {
-    "alloc_kernel_memory(size=%08x)",
-    "free_kernel_memory(buf=%08x)",
-    "init_timer(t=%08x,reload=%08x,flags=%08x)",
-    "get_timer(t=%08x)",
-    "enable_timer_irq(t=%08x)",
-    "disable_timer_irq(t=%08x)",
-    "restart_timer(t=%08x)",
-    "DeliverEvent(class=%08x, spec=%08x)",
-    "OpenEvent(class=%08x,spec=%08x,mode=%08x,func=%08x)",
-    "CloseEvent(event=%08x)",
-    "WaitEvent(event=%08x)",
-    "TestEvent(event=%08x)",
-    "EnableEvent(event=%08x)",
-    "DisableEvent(event=%08x)",
-    "OpenTh(reg_PC=%08x,reg_SP_FP=%08x,reg_GP=%08x)",
-    "CloseTh(handle=%08x)",
-    "ChangeTh(handle=%08x)",
-    "jump_to_00000000h()",
-    "InitPAD2(buf1=%08x,siz1=%08x,buf2=%08x,siz2=%08x)",
-    "StartPAD2()",
-    "StopPAD2()",
-    "PAD_init2(type=%08x,button_dest=%08x,unused=%08x,unused=%08x)",
-    "PAD_dr()",
-    "ReturnFromException()",
-    "ResetEntryInt()",
-    "HookEntryInt(addr=%08x)",
-    "SystemError()",
-    "SystemError()",
-    "SystemError()",
-    "SystemError()",
-    "SystemError()",
-    "SystemError()",
-    "UnDeliverEvent(class=%08x,spec=%08x)",
-    "SystemError()",
-    "SystemError()",
-    "SystemError()",
-    "jump_to_00000000h()",
-    "jump_to_00000000h()",
-    "jump_to_00000000h()",
-    "jump_to_00000000h()",
-    "jump_to_00000000h()",
-    "jump_to_00000000h()",
-    "SystemError()",
-    "SystemError()",
-    "jump_to_00000000h()",
-    "jump_to_00000000h()",
-    "jump_to_00000000h()",
-    "jump_to_00000000h()",
-    "jump_to_00000000h()",
-    "jump_to_00000000h()",
-    "open(filename=%08x,accessmode=%08x)",
-    "lseek(fd=%08x,offset=%08x,seektype=%08x)",
-    "read(fd=%08x,dst=%08x,length=%08x)",
-    "write(fd=%08x,src=%08x,length=%08x)",
-    "close(fd=%08x)",
-    "ioctl(fd=%08x,cmd=%08x,arg=%08x)",
-    "exit(exitcode=%08x)",
-    "isatty(fd=%08x)",
-    "getc(fd=%08x)",
-    "putc(char=%08x,fd=%08x)",
-    "getchar()",
-    "putchar(char=%08x)",
-    "gets(dst=%08x)",
-    "puts(src=%08x)",
-    "cd(name=%08x)",
-    "format(devicename=%08x)",
-    "firstfile2(filename=%08x,direntry=%08x)",
-    "nextfile(direntry=%08x)",
-    "rename(old_filename=%08x,new_filename=%08x)",
-    "erase(filename=%08x)",
-    "undelete(filename=%08x)",
-    "AddDrv(device_info=%08x)",
-    "DelDrv(device_name_lowercase=%08x)",
-    "PrintInstalledDevices()",
-    "InitCARD2(pad_enable=%08x)",
-    "StartCARD2()",
-    "StopCARD2()",
-    "_card_info_subfunc(port=%08x)",
-    "_card_write(port=%08x,sector=%08x,src=%08x)",
-    "_card_read(port=%08x,sector=%08x,dst=%08x)",
-    "_new_card()",
-    "Krom2RawAdd(shiftjis_code=%08x)",
-    "SystemError()",
-    "Krom2Offset(shiftjis_code=%08x)",
-    "_get_errno()",
-    "_get_error(fd=%08x)",
-    "GetC0Table()",
-    "GetB0Table()",
-    "_card_chan()",
-    "testdevice(devicename=%08x)",
-    "SystemError()",
-    "ChangeClearPAD(int=%08x)",
-    "_card_status(slot=%08x)",
-    "_card_wait(slot=%08x)"
-};
-
-static const char* g_psx_cpu_c_kcall_symtable[] = {
-    "EnqueueTimerAndVblankIrqs(priority=%08x)",
-    "EnqueueSyscallHandler(priority=%08x)",
-    "SysEnqIntRP(priority=%08x,struc=%08x)",
-    "SysDeqIntRP(priority=%08x,struc=%08x)",
-    "get_free_EvCB_slot()",
-    "get_free_TCB_slot()",
-    "ExceptionHandler()",
-    "InstallExceptionHandlers()",
-    "SysInitMemory(addr=%08x,size=%08x)",
-    "SysInitKernelVariables()",
-    "ChangeClearRCnt(t=%08x,flag=%08x)",
-    "SystemError()",
-    "InitDefInt(priority=%08x)",
-    "SetIrqAutoAck(irq=%08x,flag=%08x)",
-    "return 0",
-    "return 0",
-    "return 0",
-    "return 0",
-    "InstallDevices(ttyflag=%08x)",
-    "FlushStdInOutPut()",
-    "return 0",
-    "_cdevinput(circ=%08x,char=%08x)",
-    "_cdevscan()",
-    "_circgetc(circ=%08x)",
-    "_circputc(char=%08x,circ=%08x)",
-    "_ioabort(txt1=%08x,txt2=%08x)",
-    "set_card_find_mode(mode=%08x)",
-    "KernelRedirect(ttyflag=%08x)",
-    "AdjustA0Table()",
-    "get_card_find_mode()"
-};
-
-#define TRACE_M(m) \
-    log_trace("%08x: %-7s $%s, %+i($%s)", cpu->pc-8, m, g_mips_cc_register_names[T], IMM16S, g_mips_cc_register_names[S])
-
-#define TRACE_I16S(m) \
-    log_trace("%08x: %-7s $%s, 0x%04x", cpu->pc-8, m, g_mips_cc_register_names[T], IMM16)
-
-#define TRACE_I16D(m) \
-    log_trace("%08x: %-7s $%s, $%s, 0x%04x", cpu->pc-8, m, g_mips_cc_register_names[T], g_mips_cc_register_names[S], IMM16)
-
-#define TRACE_I5D(m) \
-    log_trace("%08x: %-7s $%s, $%s, %u", cpu->pc-8, m, g_mips_cc_register_names[D], g_mips_cc_register_names[T], IMM5)
-
-#define TRACE_I26(m) \
-    log_trace("%08x: %-7s 0x%07x", cpu->pc-8, m, ((cpu->pc & 0xf0000000) | (IMM26 << 2)))
-
-#define TRACE_RT(m) \
-    log_trace("%08x: %-7s $%s, $%s, $%s", cpu->pc-8, m, g_mips_cc_register_names[D], g_mips_cc_register_names[S], g_mips_cc_register_names[T])
-
-#define TRACE_C0M(m) \
-    log_trace("%08x: %-7s $%s, $%s", cpu->pc-8, m, g_mips_cc_register_names[T], g_mips_cop0_register_names[D])
-
-#define TRACE_C2M(m) \
-    log_trace("%08x: %-7s $%s, $cop2_r%u", cpu->pc-8, m, g_mips_cc_register_names[T], D)
-
-#define TRACE_C2MC(m) \
-    log_trace("%08x: %-7s $%s, $cop2_r%u", cpu->pc-8, m, g_mips_cc_register_names[T], D + 32)
-
-#define TRACE_B(m) \
-    log_trace("%08x: %-7s $%s, $%s, %-i", cpu->pc-8, m, g_mips_cc_register_names[S], g_mips_cc_register_names[T], IMM16S << 2)
-
-#define TRACE_RS(m) \
-    log_trace("%08x: %-7s $%s", cpu->pc-8, m, g_mips_cc_register_names[S])
-
-#define TRACE_MTF(m) \
-    log_trace("%08x: %-7s $%s", cpu->pc-8, m, g_mips_cc_register_names[D])
-
-#define TRACE_RD(m) \
-    log_trace("%08x: %-7s $%s, $%s", cpu->pc-8, m, g_mips_cc_register_names[D], g_mips_cc_register_names[S])
-
-#define TRACE_MD(m) \
-    log_trace("%08x: %-7s $%s, $%s", cpu->pc-8, m, g_mips_cc_register_names[S], g_mips_cc_register_names[T]);
-
-#define TRACE_I20(m) \
-    log_trace("%08x: %-7s 0x%05x", cpu->pc-8, m, CMT);
-
-#define TRACE_N(m) \
-    log_trace("%08x: %-7s", cpu->pc-8, m);
-
-#define DEBUG_ALL \
-    log_fatal("r0=%08x at=%08x v0=%08x v1=%08x", cpu->r[0] , cpu->r[1] , cpu->r[2] , cpu->r[3] ); \
-    log_fatal("a0=%08x a1=%08x a2=%08x a3=%08x", cpu->r[4] , cpu->r[5] , cpu->r[6] , cpu->r[7] ); \
-    log_fatal("t0=%08x t1=%08x t2=%08x t3=%08x", cpu->r[8] , cpu->r[9] , cpu->r[10], cpu->r[11]); \
-    log_fatal("t4=%08x t5=%08x t6=%08x t7=%08x", cpu->r[12], cpu->r[13], cpu->r[14], cpu->r[15]); \
-    log_fatal("s0=%08x s1=%08x s2=%08x s3=%08x", cpu->r[16], cpu->r[17], cpu->r[18], cpu->r[19]); \
-    log_fatal("s4=%08x s5=%08x s6=%08x s7=%08x", cpu->r[20], cpu->r[21], cpu->r[22], cpu->r[23]); \
-    log_fatal("t8=%08x t9=%08x k0=%08x k1=%08x", cpu->r[24], cpu->r[25], cpu->r[26], cpu->r[27]); \
-    log_fatal("gp=%08x sp=%08x fp=%08x ra=%08x", cpu->r[28], cpu->r[29], cpu->r[30], cpu->r[31]); \
-    log_fatal("pc=%08x hi=%08x lo=%08x l:%s=%08x", cpu->pc, cpu->hi, cpu->lo, g_mips_cc_register_names[cpu->load_d], cpu->load_v); \
-    exit(1)
-
-const char* g_mips_cop0_register_names[] = {
-    "cop0_r0",
-    "cop0_r1",
-    "cop0_r2",
-    "cop0_bpc",
-    "cop0_r4",
-    "cop0_bda",
-    "cop0_jumpdest",
-    "cop0_dcic",
-    "cop0_badvaddr",
-    "cop0_bdam",
-    "cop0_r10",
-    "cop0_bpcm",
-    "cop0_sr",
-    "cop0_cause",
-    "cop0_epc",
-    "cop0_prid"
-};
-
-static const char* g_mips_cc_register_names[] = {
-    "r0", "at", "v0", "v1", "a0", "a1", "a2", "a3",
-    "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7",
-    "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7",
-    "t8", "t9", "k0", "k1", "gp", "sp", "fp", "ra"
-};
-
-static const char* g_psx_cpu_syscall_function_symbol_table[] = {
-    "NoFunction",
-    "EnterCriticalSection",
-    "ExitCriticalSection",
-    "ChangeThreadSubFunction"
-    // DeliverEvent (invalid)
-};
-
-#else
-#define TRACE_M(m)
-#define TRACE_I16S(m)
-#define TRACE_I16D(m)
-#define TRACE_I5D(m)
-#define TRACE_I26(m)
-#define TRACE_RT(m)
-#define TRACE_C0M(m)
-#define TRACE_C2M(m)
-#define TRACE_C2MC(m)
-#define TRACE_B(m)
-#define TRACE_RS(m)
-#define TRACE_MTF(m)
-#define TRACE_RD(m)
-#define TRACE_MD(m)
-#define TRACE_I20(m)
-#define TRACE_N(m)
+#ifdef CPU_TRACE
+
+static const char* g_psx_cpu_a_kcall_symtable[] = {
+    "open(filename=%08x,accessmode=%08x)",
+    "lseek(fd=%08x,offset=%08x,seektype=%08x)",
+    "read(fd=%08x,dst=%08x,length=%08x)",
+    "write(fd=%08x,src=%08x,length=%08x)",
+    "close(fd=%08x)",
+    "ioctl(fd=%08x,cmd=%08x,arg=%08x)",
+    "exit(exitcode=%08x)",
+    "isatty(fd=%08x)",
+    "getc(fd=%08x)",
+    "putc(char=%08x,fd=%08x)",
+    "todigit(char=%08x)",
+    "atof(src=%08x)",
+    "strtoul(src=%08x,src_end=%08x,base=%08x)",
+    "strtol(src=%08x,src_end=%08x,base=%08x)",
+    "abs(val=%08x)",
+    "labs(val=%08x)",
+    "atoi(src=%08x)",
+    "atol(src=%08x)",
+    "atob(src=%08x,num_dst=%08x)",
+    "setjmp(buf=%08x)",
+    "longjmp(buf=%08x,param=%08x)",
+    "strcat(dst=%08x,src=%08x)",
+    "strncat(dst=%08x,src=%08x,maxlen=%08x)",
+    "strcmp(str1=%08x,str2=%08x)",
+    "strncmp(str1=%08x,str2=%08x,maxlen=%08x)",
+    "strcpy(dst=%08x,src=%08x)",
+    "strncpy(dst=%08x,src=%08x,maxlen=%08x)",
+    "strlen(src=%08x)",
+    "index(src=%08x,char=%08x)",
+    "rindex(src=%08x,char=%08x)",
+    "strchr(src=%08x,char=%08x)",
+    "strrchr(src=%08x,char=%08x)",
+    "strpbrk(src=%08x,list=%08x)",
+    "strspn(src=%08x,list=%08x)",
+    "strcspn(src=%08x,list=%08x)",
+    "strtok(src=%08x,list=%08x)",
+    "strstr(str=%08x,substr=%08x)",
+    "toupper(char=%08x)",
+    "tolower(char=%08x)",
+    "bcopy(src=%08x,dst=%08x,len=%08x)",
+    "bzero(dst=%08x,len=%08x)",
+    "bcmp(ptr1=%08x,ptr2=%08x,len=%08x)",
+    "memcpy(dst=%08x,src=%08x,len=%08x)",
+    "memset(dst=%08x,fillbyte=%08x,len=%08x)",
+    "memmove(dst=%08x,src=%08x,len=%08x)",
+    "memcmp(src1=%08x,src2=%08x,len=%08x)",
+    "memchr(src=%08x,scanbyte=%08x,len=%08x)",
+    "rand()",
+    "srand(seed=%08x)",
+    "qsort(base=%08x,nel=%08x,width=%08x,callback=%08x)",
+    "strtod(src=%08x,src_end=%08x)",
+    "malloc(size=%08x)",
+    "free(buf=%08x)",
+    "lsearch(key=%08x,base=%08x,nel=%08x,width=%08x,callback=%08x)",
+    "bsearch(key=%08x,base=%08x,nel=%08x,width=%08x,callback=%08x)",
+    "calloc(sizx=%08x,sizy=%08x)",
+    "realloc(old_buf=%08x,new_siz=%08x)",
+    "InitHeap(addr=%08x,size=%08x)",
+    "_exit(exitcode=%08x)",
+    "getchar()",
+    "putchar(char=%08x)",
+    "gets(dst=%08x)",
+    "puts(src=%08x)",
+    "printf(txt=%08x,param1=%08x,param2=%08x,etc.=%08x)",
+    "SystemErrorUnresolvedException()",
+    "LoadTest(filename=%08x,headerbuf=%08x)",
+    "Load(filename=%08x,headerbuf=%08x)",
+    "Exec(headerbuf=%08x,param1=%08x,param2=%08x)",
+    "FlushCache()",
+    "init_a0_b0_c0_vectors()",
+    "GPU_dw(Xdst=%08x,Ydst=%08x,Xsiz=%08x,Ysiz=%08x,src=%08x)",
+    "gpu_send_dma(Xdst=%08x,Ydst=%08x,Xsiz=%08x,Ysiz=%08x,src=%08x)",
+    "SendGP1Command(gp1cmd=%08x)",
+    "GPU_cw(gp0cmd=%08x)",
+    "GPU_cwp(src=%08x,num=%08x)",
+    "send_gpu_linked_list(src=%08x)",
+    "gpu_abort_dma()",
+    "GetGPUStatus()",
+    "gpu_sync()",
+    "SystemError()",
+    "SystemError()",
+    "LoadExec(filename=%08x,stackbase=%08x,stackoffset=%08x)",
+    "GetSysSp()",
+    "SystemError()",
+    "_96_init()",
+    "_bu_init()",
+    "_96_remove()",
+    "return 0",
+    "return 0",
+    "return 0",
+    "return 0",
+    "dev_tty_init()",
+    "dev_tty_open(fcb=%08x, (unused)path=%08x,accessmode=%08x)",
+    "dev_tty_in_out(fcb=%08x,cmd=%08x)",
+    "dev_tty_ioctl(fcb=%08x,cmd=%08x,arg=%08x)",
+    "dev_cd_open(fcb=%08x,path=%08x,accessmode=%08x)",
+    "dev_cd_read(fcb=%08x,dst=%08x,len=%08x)",
+    "dev_cd_close(fcb=%08x)",
+    "dev_cd_firstfile(fcb=%08x,path=%08x,direntry=%08x)",
+    "dev_cd_nextfile(fcb=%08x,direntry=%08x)",
+    "dev_cd_chdir(fcb=%08x,path=%08x)",
+    "dev_card_open(fcb=%08x,path=%08x,accessmode=%08x)",
+    "dev_card_read(fcb=%08x,dst=%08x,len=%08x)",
+    "dev_card_write(fcb=%08x,src=%08x,len=%08x)",
+    "dev_card_close(fcb=%08x)",
+    "dev_card_firstfile(fcb=%08x,path=%08x,direntry=%08x)",
+    "dev_card_nextfile(fcb=%08x,direntry=%08x)",
+    "dev_card_erase(fcb=%08x,path=%08x)",
+    "dev_card_undelete(fcb=%08x,path=%08x)",
+    "dev_card_format(fcb=%08x)",
+    "dev_card_rename(fcb1=%08x,path=%08x)",
+    "card_clear_error(fcb=%08x) (?)",
+    "_bu_init()",
+    "_96_init()",
+    "_96_remove()",
+    "return 0",
+    "return 0",
+    "return 0",
+    "return 0",
+    "return 0",
+    "CdAsyncSeekL(src=%08x)",
+    "return 0",
+    "return 0",
+    "return 0",
+    "CdAsyncGetStatus(dst=%08x)",
+    "return 0",
+    "CdAsyncReadSector(count=%08x,dst=%08x,mode=%08x)",
+    "return 0",
+    "return 0",
+    "CdAsyncSetMode(mode=%08x)",
+    "return 0",
+    "return 0",
+    "return 0",
+    "return 0",
+    "return 0",
+    "return 0",
+    "return 0",
+    "return 0",
+    "return 0",
+    "return 0",
+    "return 0",
+    "return 0",
+    "return 0",
+    "return 0",
+    "CdromIoIrqFunc1()",
+    "CdromDmaIrqFunc1()",
+    "CdromIoIrqFunc2()",
+    "CdromDmaIrqFunc2()",
+    "CdromGetInt5errCode(dst1=%08x,dst2=%08x)",
+    "CdInitSubFunc()",
+    "AddCDROMDevice()",
+    "AddMemCardDevice()",
+    "AddDuartTtyDevice()",
+    "add_nullcon_driver()",
+    "SystemError()",
+    "SystemError()",
+    "SetConf(num_EvCB=%08x,num_TCB=%08x,stacktop=%08x)",
+    "GetConf(num_EvCB_dst=%08x,num_TCB_dst=%08x,stacktop_dst=%08x)",
+    "SetCdromIrqAutoAbort(type=%08x,flag=%08x)",
+    "SetMem(megabytes=%08x)",
+    "_boot()",
+    "SystemError(type=%08x,errorcode=%08x)",
+    "EnqueueCdIntr()",
+    "DequeueCdIntr()",
+    "CdGetLbn(filename=%08x)",
+    "CdReadSector(count=%08x,sector=%08x,buffer=%08x)",
+    "CdGetStatus()",
+    "bufs_cb_0()",
+    "bufs_cb_1()",
+    "bufs_cb_2()",
+    "bufs_cb_3()",
+    "_card_info(port=%08x)",
+    "_card_load(port=%08x)",
+    "_card_auto(flag=%08x)",
+    "bufs_cb_4()",
+    "card_write_test(port=%08x)",
+    "return 0",
+    "return 0",
+    "ioabort_raw(param=%08x)",
+    "return 0",
+    "GetSystemInfo(index=%08x)"
+};
+
+static const char* g_psx_cpu_b_kcall_symtable[] = {
+    "alloc_kernel_memory(size=%08x)",
+    "free_kernel_memory(buf=%08x)",
+    "init_timer(t=%08x,reload=%08x,flags=%08x)",
+    "get_timer(t=%08x)",
+    "enable_timer_irq(t=%08x)",
+    "disable_timer_irq(t=%08x)",
+    "restart_timer(t=%08x)",
+    "DeliverEvent(class=%08x, spec=%08x)",
+    "OpenEvent(class=%08x,spec=%08x,mode=%08x,func=%08x)",
+    "CloseEvent(event=%08x)",
+    "WaitEvent(event=%08x)",
+    "TestEvent(event=%08x)",
+    "EnableEvent(event=%08x)",
+    "DisableEvent(event=%08x)",
+    "OpenTh(reg_PC=%08x,reg_SP_FP=%08x,reg_GP=%08x)",
+    "CloseTh(handle=%08x)",
+    "ChangeTh(handle=%08x)",
+    "jump_to_00000000h()",
+    "InitPAD2(buf1=%08x,siz1=%08x,buf2=%08x,siz2=%08x)",
+    "StartPAD2()",
+    "StopPAD2()",
+    "PAD_init2(type=%08x,button_dest=%08x,unused=%08x,unused=%08x)",
+    "PAD_dr()",
+    "ReturnFromException()",
+    "ResetEntryInt()",
+    "HookEntryInt(addr=%08x)",
+    "SystemError()",
+    "SystemError()",
+    "SystemError()",
+    "SystemError()",
+    "SystemError()",
+    "SystemError()",
+    "UnDeliverEvent(class=%08x,spec=%08x)",
+    "SystemError()",
+    "SystemError()",
+    "SystemError()",
+    "jump_to_00000000h()",
+    "jump_to_00000000h()",
+    "jump_to_00000000h()",
+    "jump_to_00000000h()",
+    "jump_to_00000000h()",
+    "jump_to_00000000h()",
+    "SystemError()",
+    "SystemError()",
+    "jump_to_00000000h()",
+    "jump_to_00000000h()",
+    "jump_to_00000000h()",
+    "jump_to_00000000h()",
+    "jump_to_00000000h()",
+    "jump_to_00000000h()",
+    "open(filename=%08x,accessmode=%08x)",
+    "lseek(fd=%08x,offset=%08x,seektype=%08x)",
+    "read(fd=%08x,dst=%08x,length=%08x)",
+    "write(fd=%08x,src=%08x,length=%08x)",
+    "close(fd=%08x)",
+    "ioctl(fd=%08x,cmd=%08x,arg=%08x)",
+    "exit(exitcode=%08x)",
+    "isatty(fd=%08x)",
+    "getc(fd=%08x)",
+    "putc(char=%08x,fd=%08x)",
+    "getchar()",
+    "putchar(char=%08x)",
+    "gets(dst=%08x)",
+    "puts(src=%08x)",
+    "cd(name=%08x)",
+    "format(devicename=%08x)",
+    "firstfile2(filename=%08x,direntry=%08x)",
+    "nextfile(direntry=%08x)",
+    "rename(old_filename=%08x,new_filename=%08x)",
+    "erase(filename=%08x)",
+    "undelete(filename=%08x)",
+    "AddDrv(device_info=%08x)",
+    "DelDrv(device_name_lowercase=%08x)",
+    "PrintInstalledDevices()",
+    "InitCARD2(pad_enable=%08x)",
+    "StartCARD2()",
+    "StopCARD2()",
+    "_card_info_subfunc(port=%08x)",
+    "_card_write(port=%08x,sector=%08x,src=%08x)",
+    "_card_read(port=%08x,sector=%08x,dst=%08x)",
+    "_new_card()",
+    "Krom2RawAdd(shiftjis_code=%08x)",
+    "SystemError()",
+    "Krom2Offset(shiftjis_code=%08x)",
+    "_get_errno()",
+    "_get_error(fd=%08x)",
+    "GetC0Table()",
+    "GetB0Table()",
+    "_card_chan()",
+    "testdevice(devicename=%08x)",
+    "SystemError()",
+    "ChangeClearPAD(int=%08x)",
+    "_card_status(slot=%08x)",
+    "_card_wait(slot=%08x)"
+};
+
+static const char* g_psx_cpu_c_kcall_symtable[] = {
+    "EnqueueTimerAndVblankIrqs(priority=%08x)",
+    "EnqueueSyscallHandler(priority=%08x)",
+    "SysEnqIntRP(priority=%08x,struc=%08x)",
+    "SysDeqIntRP(priority=%08x,struc=%08x)",
+    "get_free_EvCB_slot()",
+    "get_free_TCB_slot()",
+    "ExceptionHandler()",
+    "InstallExceptionHandlers()",
+    "SysInitMemory(addr=%08x,size=%08x)",
+    "SysInitKernelVariables()",
+    "ChangeClearRCnt(t=%08x,flag=%08x)",
+    "SystemError()",
+    "InitDefInt(priority=%08x)",
+    "SetIrqAutoAck(irq=%08x,flag=%08x)",
+    "return 0",
+    "return 0",
+    "return 0",
+    "return 0",
+    "InstallDevices(ttyflag=%08x)",
+    "FlushStdInOutPut()",
+    "return 0",
+    "_cdevinput(circ=%08x,char=%08x)",
+    "_cdevscan()",
+    "_circgetc(circ=%08x)",
+    "_circputc(char=%08x,circ=%08x)",
+    "_ioabort(txt1=%08x,txt2=%08x)",
+    "set_card_find_mode(mode=%08x)",
+    "KernelRedirect(ttyflag=%08x)",
+    "AdjustA0Table()",
+    "get_card_find_mode()"
+};
+
+#define TRACE_M(m) \
+    log_trace("%08x: %-7s $%s, %+i($%s)", cpu->pc-8, m, g_mips_cc_register_names[T], IMM16S, g_mips_cc_register_names[S])
+
+#define TRACE_I16S(m) \
+    log_trace("%08x: %-7s $%s, 0x%04x", cpu->pc-8, m, g_mips_cc_register_names[T], IMM16)
+
+#define TRACE_I16D(m) \
+    log_trace("%08x: %-7s $%s, $%s, 0x%04x", cpu->pc-8, m, g_mips_cc_register_names[T], g_mips_cc_register_names[S], IMM16)
+
+#define TRACE_I5D(m) \
+    log_trace("%08x: %-7s $%s, $%s, %u", cpu->pc-8, m, g_mips_cc_register_names[D], g_mips_cc_register_names[T], IMM5)
+
+#define TRACE_I26(m) \
+    log_trace("%08x: %-7s 0x%07x", cpu->pc-8, m, ((cpu->pc & 0xf0000000) | (IMM26 << 2)))
+
+#define TRACE_RT(m) \
+    log_trace("%08x: %-7s $%s, $%s, $%s", cpu->pc-8, m, g_mips_cc_register_names[D], g_mips_cc_register_names[S], g_mips_cc_register_names[T])
+
+#define TRACE_C0M(m) \
+    log_trace("%08x: %-7s $%s, $%s", cpu->pc-8, m, g_mips_cc_register_names[T], g_mips_cop0_register_names[D])
+
+#define TRACE_C2M(m) \
+    log_trace("%08x: %-7s $%s, $cop2_r%u", cpu->pc-8, m, g_mips_cc_register_names[T], D)
+
+#define TRACE_C2MC(m) \
+    log_trace("%08x: %-7s $%s, $cop2_r%u", cpu->pc-8, m, g_mips_cc_register_names[T], D + 32)
+
+#define TRACE_B(m) \
+    log_trace("%08x: %-7s $%s, $%s, %-i", cpu->pc-8, m, g_mips_cc_register_names[S], g_mips_cc_register_names[T], IMM16S << 2)
+
+#define TRACE_RS(m) \
+    log_trace("%08x: %-7s $%s", cpu->pc-8, m, g_mips_cc_register_names[S])
+
+#define TRACE_MTF(m) \
+    log_trace("%08x: %-7s $%s", cpu->pc-8, m, g_mips_cc_register_names[D])
+
+#define TRACE_RD(m) \
+    log_trace("%08x: %-7s $%s, $%s", cpu->pc-8, m, g_mips_cc_register_names[D], g_mips_cc_register_names[S])
+
+#define TRACE_MD(m) \
+    log_trace("%08x: %-7s $%s, $%s", cpu->pc-8, m, g_mips_cc_register_names[S], g_mips_cc_register_names[T]);
+
+#define TRACE_I20(m) \
+    log_trace("%08x: %-7s 0x%05x", cpu->pc-8, m, CMT);
+
+#define TRACE_N(m) \
+    log_trace("%08x: %-7s", cpu->pc-8, m);
+
+#define DEBUG_ALL \
+    log_fatal("r0=%08x at=%08x v0=%08x v1=%08x", cpu->r[0] , cpu->r[1] , cpu->r[2] , cpu->r[3] ); \
+    log_fatal("a0=%08x a1=%08x a2=%08x a3=%08x", cpu->r[4] , cpu->r[5] , cpu->r[6] , cpu->r[7] ); \
+    log_fatal("t0=%08x t1=%08x t2=%08x t3=%08x", cpu->r[8] , cpu->r[9] , cpu->r[10], cpu->r[11]); \
+    log_fatal("t4=%08x t5=%08x t6=%08x t7=%08x", cpu->r[12], cpu->r[13], cpu->r[14], cpu->r[15]); \
+    log_fatal("s0=%08x s1=%08x s2=%08x s3=%08x", cpu->r[16], cpu->r[17], cpu->r[18], cpu->r[19]); \
+    log_fatal("s4=%08x s5=%08x s6=%08x s7=%08x", cpu->r[20], cpu->r[21], cpu->r[22], cpu->r[23]); \
+    log_fatal("t8=%08x t9=%08x k0=%08x k1=%08x", cpu->r[24], cpu->r[25], cpu->r[26], cpu->r[27]); \
+    log_fatal("gp=%08x sp=%08x fp=%08x ra=%08x", cpu->r[28], cpu->r[29], cpu->r[30], cpu->r[31]); \
+    log_fatal("pc=%08x hi=%08x lo=%08x l:%s=%08x", cpu->pc, cpu->hi, cpu->lo, g_mips_cc_register_names[cpu->load_d], cpu->load_v); \
+    exit(1)
+
+const char* g_mips_cop0_register_names[] = {
+    "cop0_r0",
+    "cop0_r1",
+    "cop0_r2",
+    "cop0_bpc",
+    "cop0_r4",
+    "cop0_bda",
+    "cop0_jumpdest",
+    "cop0_dcic",
+    "cop0_badvaddr",
+    "cop0_bdam",
+    "cop0_r10",
+    "cop0_bpcm",
+    "cop0_sr",
+    "cop0_cause",
+    "cop0_epc",
+    "cop0_prid"
+};
+
+static const char* g_mips_cc_register_names[] = {
+    "r0", "at", "v0", "v1", "a0", "a1", "a2", "a3",
+    "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7",
+    "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7",
+    "t8", "t9", "k0", "k1", "gp", "sp", "fp", "ra"
+};
+
+static const char* g_psx_cpu_syscall_function_symbol_table[] = {
+    "NoFunction",
+    "EnterCriticalSection",
+    "ExitCriticalSection",
+    "ChangeThreadSubFunction"
+    // DeliverEvent (invalid)
+};
+
+#else
+#define TRACE_M(m)
+#define TRACE_I16S(m)
+#define TRACE_I16D(m)
+#define TRACE_I5D(m)
+#define TRACE_I26(m)
+#define TRACE_RT(m)
+#define TRACE_C0M(m)
+#define TRACE_C2M(m)
+#define TRACE_C2MC(m)
+#define TRACE_B(m)
+#define TRACE_RS(m)
+#define TRACE_MTF(m)
+#define TRACE_RD(m)
+#define TRACE_MD(m)
+#define TRACE_I20(m)
+#define TRACE_N(m)
 #endif
\ No newline at end of file
--- a/psx/dev/bios.c
+++ b/psx/dev/bios.c
@@ -1,81 +1,81 @@
-#include "bios.h"
-#include "../log.h"
-
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-
-psx_bios_t* psx_bios_create() {
-    return (psx_bios_t*)malloc(sizeof(psx_bios_t));
-}
-
-void psx_bios_init(psx_bios_t* bios) {
-    memset(bios, 0, sizeof(psx_bios_t));
-
-    bios->io_base = PSX_BIOS_BEGIN;
-    bios->io_size = PSX_BIOS_SIZE;
-    bios->bus_delay = 18;
-}
-
-void psx_bios_load(psx_bios_t* bios, const char* path) {
-    FILE* file = fopen(path, "rb");
-
-    if (!file) {
-        log_error("Couldn't open BIOS file \"%s\"", path);
-
-        exit(1);
-    }
-
-    // Almost all PS1 BIOS ROMs are 512 KiB in size.
-    // There's (at least) one exception, and that is SCPH-5903.
-    // This is a special asian model PS1 that had built-in support
-    // for Video CD (VCD) playback. Its BIOS is double the normal
-    // size
-    fseek(file, 0, SEEK_END);
-
-    size_t size = ftell(file);
-
-    fseek(file, 0, SEEK_SET);
-
-    bios->buf = malloc(size);
-    bios->io_size = size;
-
-    if (!fread(bios->buf, 1, size, file)) {
-        perror("Error reading BIOS file");
-
-        exit(1);
-    }
-
-    log_info("Loaded BIOS file \"%s\"", path);
-
-    fclose(file);
-}
-
-uint32_t psx_bios_read32(psx_bios_t* bios, uint32_t offset) {
-    return *((uint32_t*)(bios->buf + offset));
-}
-
-uint16_t psx_bios_read16(psx_bios_t* bios, uint32_t offset) {
-    return *((uint16_t*)(bios->buf + offset));
-}
-
-uint8_t psx_bios_read8(psx_bios_t* bios, uint32_t offset) {
-    return bios->buf[offset];
-}
-
-void psx_bios_write32(psx_bios_t* bios, uint32_t offset, uint32_t value) {
-    log_warn("Unhandled 32-bit BIOS write at offset %08x (%08x)", offset, value);
-}
-
-void psx_bios_write16(psx_bios_t* bios, uint32_t offset, uint16_t value) {
-    log_warn("Unhandled 16-bit BIOS write at offset %08x (%04x)", offset, value);
-}
-
-void psx_bios_write8(psx_bios_t* bios, uint32_t offset, uint8_t value) {
-    log_warn("Unhandled 8-bit BIOS write at offset %08x (%02x)", offset, value);
-}
-
-void psx_bios_destroy(psx_bios_t* bios) {
-    free(bios->buf);
-    free(bios);
+#include "bios.h"
+#include "../log.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+psx_bios_t* psx_bios_create() {
+    return (psx_bios_t*)malloc(sizeof(psx_bios_t));
+}
+
+void psx_bios_init(psx_bios_t* bios) {
+    memset(bios, 0, sizeof(psx_bios_t));
+
+    bios->io_base = PSX_BIOS_BEGIN;
+    bios->io_size = PSX_BIOS_SIZE;
+    bios->bus_delay = 18;
+}
+
+void psx_bios_load(psx_bios_t* bios, const char* path) {
+    FILE* file = fopen(path, "rb");
+
+    if (!file) {
+        log_error("Couldn't open BIOS file \"%s\"", path);
+
+        exit(1);
+    }
+
+    // Almost all PS1 BIOS ROMs are 512 KiB in size.
+    // There's (at least) one exception, and that is SCPH-5903.
+    // This is a special asian model PS1 that had built-in support
+    // for Video CD (VCD) playback. Its BIOS is double the normal
+    // size
+    fseek(file, 0, SEEK_END);
+
+    size_t size = ftell(file);
+
+    fseek(file, 0, SEEK_SET);
+
+    bios->buf = malloc(size);
+    bios->io_size = size;
+
+    if (!fread(bios->buf, 1, size, file)) {
+        perror("Error reading BIOS file");
+
+        exit(1);
+    }
+
+    log_info("Loaded BIOS file \"%s\"", path);
+
+    fclose(file);
+}
+
+uint32_t psx_bios_read32(psx_bios_t* bios, uint32_t offset) {
+    return *((uint32_t*)(bios->buf + offset));
+}
+
+uint16_t psx_bios_read16(psx_bios_t* bios, uint32_t offset) {
+    return *((uint16_t*)(bios->buf + offset));
+}
+
+uint8_t psx_bios_read8(psx_bios_t* bios, uint32_t offset) {
+    return bios->buf[offset];
+}
+
+void psx_bios_write32(psx_bios_t* bios, uint32_t offset, uint32_t value) {
+    log_warn("Unhandled 32-bit BIOS write at offset %08x (%08x)", offset, value);
+}
+
+void psx_bios_write16(psx_bios_t* bios, uint32_t offset, uint16_t value) {
+    log_warn("Unhandled 16-bit BIOS write at offset %08x (%04x)", offset, value);
+}
+
+void psx_bios_write8(psx_bios_t* bios, uint32_t offset, uint8_t value) {
+    log_warn("Unhandled 8-bit BIOS write at offset %08x (%02x)", offset, value);
+}
+
+void psx_bios_destroy(psx_bios_t* bios) {
+    free(bios->buf);
+    free(bios);
 }
\ No newline at end of file
--- a/psx/dev/bios.h
+++ b/psx/dev/bios.h
@@ -1,30 +1,30 @@
-#ifndef BIOS_H
-#define BIOS_H
-
-#include <stdint.h>
-
-#include "../log.h"
-
-#define PSX_BIOS_SIZE   0x80000
-#define PSX_BIOS_BEGIN  0x1fc00000
-#define PSX_BIOS_END    0x1fc7ffff
-
-typedef struct {
-    uint32_t bus_delay;
-    uint32_t io_base, io_size;
-
-    uint8_t* buf;
-} psx_bios_t;
-
-psx_bios_t* psx_bios_create();
-void psx_bios_init(psx_bios_t*);
-void psx_bios_load(psx_bios_t*, const char*);
-uint32_t psx_bios_read32(psx_bios_t*, uint32_t);
-uint16_t psx_bios_read16(psx_bios_t*, uint32_t);
-uint8_t psx_bios_read8(psx_bios_t*, uint32_t);
-void psx_bios_write32(psx_bios_t*, uint32_t, uint32_t);
-void psx_bios_write16(psx_bios_t*, uint32_t, uint16_t);
-void psx_bios_write8(psx_bios_t*, uint32_t, uint8_t);
-void psx_bios_destroy(psx_bios_t*);
-
+#ifndef BIOS_H
+#define BIOS_H
+
+#include <stdint.h>
+
+#include "../log.h"
+
+#define PSX_BIOS_SIZE   0x80000
+#define PSX_BIOS_BEGIN  0x1fc00000
+#define PSX_BIOS_END    0x1fc7ffff
+
+typedef struct {
+    uint32_t bus_delay;
+    uint32_t io_base, io_size;
+
+    uint8_t* buf;
+} psx_bios_t;
+
+psx_bios_t* psx_bios_create();
+void psx_bios_init(psx_bios_t*);
+void psx_bios_load(psx_bios_t*, const char*);
+uint32_t psx_bios_read32(psx_bios_t*, uint32_t);
+uint16_t psx_bios_read16(psx_bios_t*, uint32_t);
+uint8_t psx_bios_read8(psx_bios_t*, uint32_t);
+void psx_bios_write32(psx_bios_t*, uint32_t, uint32_t);
+void psx_bios_write16(psx_bios_t*, uint32_t, uint16_t);
+void psx_bios_write8(psx_bios_t*, uint32_t, uint8_t);
+void psx_bios_destroy(psx_bios_t*);
+
 #endif
\ No newline at end of file
--- a/psx/dev/cdrom.c
+++ b/psx/dev/cdrom.c
@@ -1,2217 +1,2205 @@
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
-
-#include "cdrom.h"
-#include "../log.h"
-#include "../msf.h"
-
-/*
-    Drive Status           1st Response   2nd Response
-    Door Open              INT5(11h,80h)  N/A
-    Spin-up                INT5(01h,80h)  N/A
-    Detect busy            INT5(03h,80h)  N/A
-    No Disk                INT3(stat)     INT5(08h,40h, 00h,00h, 00h,00h,00h,00h)
-    Audio Disk             INT3(stat)     INT5(0Ah,90h, 00h,00h, 00h,00h,00h,00h)
-    Unlicensed:Mode1       INT3(stat)     INT5(0Ah,80h, 00h,00h, 00h,00h,00h,00h)
-    Unlicensed:Mode2       INT3(stat)     INT5(0Ah,80h, 20h,00h, 00h,00h,00h,00h)
-    Unlicensed:Mode2+Audio INT3(stat)     INT5(0Ah,90h, 20h,00h, 00h,00h,00h,00h)
-    Debug/Yaroze:Mode2     INT3(stat)     INT2(02h,00h, 20h,00h, 20h,20h,20h,20h)
-    Licensed:Mode2         INT3(stat)     INT2(02h,00h, 20h,00h, 53h,43h,45h,4xh)
-    Modchip:Audio/Mode1    INT3(stat)     INT2(02h,00h, 00h,00h, 53h,43h,45h,4xh)
-*/
-
-msf_t cdrom_get_track_addr(psx_cdrom_t* cdrom, msf_t msf) {
-    uint32_t lba = msf_to_address(msf);
-
-    int num_tracks, track;
-
-    psx_disc_get_track_count(cdrom->disc, &num_tracks);
-
-    for (track = 1; track < num_tracks - 1; track++) {
-        msf_t curr, next;
-
-        psx_disc_get_track_addr(cdrom->disc, &curr, track);
-        psx_disc_get_track_addr(cdrom->disc, &next, track + 1);
-
-        uint32_t curr_lba = msf_to_address(curr);
-        uint32_t next_lba = msf_to_address(next);
-
-        printf("lba=%02u:%02u:%02u (%08x) curr=%02u:%02u:%02u (%08x) next=%02u:%02u:%02u (%08x)\n",
-            msf.m,
-            msf.s,
-            msf.f,
-            lba,
-            curr.m,
-            curr.s,
-            curr.f,
-            curr_lba,
-            next.m,
-            next.s,
-            next.f,
-            next_lba
-        );
-
-        if ((lba >= curr_lba) && (lba < next_lba))
-            break;
-    }
-
-    msf_t track_msf;
-
-    psx_disc_get_track_addr(cdrom->disc, &track_msf, track);
-
-    return track_msf;
-}
-
-void cdrom_fetch_video_sector(psx_cdrom_t* cdrom) {
-    while (true) {
-        if (psx_disc_seek(cdrom->disc, cdrom->seek_msf))
-            return;
-
-        psx_disc_read_sector(cdrom->disc, cdrom->dfifo);
-
-        // printf("%02u:%02u:%02u - file=%02x channel=%02x sm=%02x (%u%u%u%u%u%u%u%u) ci=%02x... ",
-        //     cdrom->seek_msf.m,
-        //     cdrom->seek_msf.s,
-        //     cdrom->seek_msf.f,
-        //     cdrom->dfifo[0x10],
-        //     cdrom->dfifo[0x11],
-        //     cdrom->dfifo[0x12],
-        //     (cdrom->dfifo[0x12] & 0x80) != 0,
-        //     (cdrom->dfifo[0x12] & 0x40) != 0,
-        //     (cdrom->dfifo[0x12] & 0x20) != 0,
-        //     (cdrom->dfifo[0x12] & 0x10) != 0,
-        //     (cdrom->dfifo[0x12] & 0x08) != 0,
-        //     (cdrom->dfifo[0x12] & 0x04) != 0,
-        //     (cdrom->dfifo[0x12] & 0x02) != 0,
-        //     (cdrom->dfifo[0x12] & 0x01) != 0,
-        //     cdrom->dfifo[0x13]
-        // );
-
-        msf_add_f(&cdrom->seek_msf, 1);
-
-        // Check RT and Video/Data bit
-        uint8_t sm = cdrom->dfifo[0x12] & 4;
-
-        if (sm) {
-            // printf("Not sent (Unfiltered)\n");
-
-            continue;
-        }
-
-        // If we get here it means this is a real-time video sector.
-        // If the XA filter is disabled, we're done
-        if (!(cdrom->mode & MODE_XA_FILTER)) {
-            // printf("Sent (Unfiltered)\n");
-            return;
-        }
-
-        // Else check XA file/channel
-        int file_eq = cdrom->dfifo[0x10] == cdrom->xa_file;
-        int channel_eq = cdrom->dfifo[0x11] == cdrom->xa_channel;
-
-        // If they are equal to our filter values, we're done
-        // else keep searching
-        if (file_eq && channel_eq) {
-            // printf("Sent (Filtered)\n");
-            return;
-        }
-
-        // printf("Not sent (Filtered)\n");
-    }
-}
-
-#define GETID_RESPONSE_SIZE 8
-#define GETID_RESPONSE_END (GETID_RESPONSE_SIZE - 1)
-
-static const int16_t g_zigzag_table0[] = {
-     0x0000,  0x0000,  0x0000,  0x0000,
-     0x0000, -0x0002,  0x000a, -0x0022,
-     0x0041, -0x0054,  0x0034,  0x0009,
-    -0x010a,  0x0400, -0x0a78,  0x234c,
-     0x6794, -0x1780,  0x0bcd, -0x0623,
-     0x0350, -0x016d,  0x006b,  0x000a,
-    -0x0010,  0x0011, -0x0008,  0x0003,
-    -0x0001
-};
-
-static const int16_t g_zigzag_table1[] = {
-     0x0000,  0x0000,  0x0000, -0x0002,
-     0x0000,  0x0003, -0x0013,  0x003c,
-    -0x004b,  0x00a2, -0x00e3,  0x0132,
-    -0x0043, -0x0267,  0x0c9d,  0x74bb,
-    -0x11b4,  0x09b8, -0x05bf,  0x0372,
-    -0x01a8,  0x00a6, -0x001b,  0x0005,
-     0x0006, -0x0008,  0x0003, -0x0001,
-     0x0000
-};
-
-static const int16_t g_zigzag_table2[] = {
-     0x0000,  0x0000, -0x0001,  0x0003,
-    -0x0002, -0x0005,  0x001f, -0x004a,
-     0x00b3, -0x0192,  0x02b1, -0x039e,
-     0x04f8, -0x05a6,  0x7939, -0x05a6,
-     0x04f8, -0x039e,  0x02b1, -0x0192,
-     0x00b3, -0x004a,  0x001f, -0x0005,
-    -0x0002,  0x0003, -0x0001,  0x0000,
-     0x0000
-};
-
-static const int16_t g_zigzag_table3[] = {
-     0x0000, -0x0001,  0x0003, -0x0008,
-     0x0006,  0x0005, -0x001b,  0x00a6,
-    -0x01a8,  0x0372, -0x05bf,  0x09b8,
-    -0x11b4,  0x74bb,  0x0c9d, -0x0267,
-    -0x0043,  0x0132, -0x00e3,  0x00a2,
-    -0x004b,  0x003c, -0x0013,  0x0003,
-     0x0000, -0x0002,  0x0000,  0x0000,
-     0x0000
-};
-
-static const int16_t g_zigzag_table4[] = {
-    -0x0001,  0x0003, -0x0008,  0x0011,
-    -0x0010,  0x000a,  0x006b, -0x016d,
-     0x0350, -0x0623,  0x0bcd, -0x1780,
-     0x6794,  0x234c, -0x0a78,  0x0400,
-    -0x010a,  0x0009,  0x0034, -0x0054,
-     0x0041, -0x0022,  0x000a, -0x0001,
-     0x0000,  0x0001,  0x0000,  0x0000,
-     0x0000
-};
-
-static const int16_t g_zigzag_table5[] = {
-     0x0002, -0x0008,  0x0010, -0x0023,
-     0x002b,  0x001a, -0x00eb,  0x027b,
-    -0x0548,  0x0afa, -0x16fa,  0x53e0,
-     0x3c07, -0x1249,  0x080e, -0x0347,
-     0x015b, -0x0044, -0x0017,  0x0046,
-    -0x0023,  0x0011, -0x0005,  0x0000,
-     0x0000,  0x0000,  0x0000,  0x0000,
-     0x0000
-};
-
-static const int16_t g_zigzag_table6[] = {
-    -0x0005,  0x0011, -0x0023,  0x0046,
-    -0x0017, -0x0044,  0x015b, -0x0347,
-     0x080e, -0x1249,  0x3c07,  0x53e0,
-    -0x16fa,  0x0afa, -0x0548,  0x027b,
-    -0x00eb,  0x001a,  0x002b, -0x0023,
-     0x0010, -0x0008,  0x0002,  0x0000,
-     0x0000,  0x0000,  0x0000,  0x0000,
-     0x0000
-};
-
-static const int16_t* g_zigzag_table[] = {
-    g_zigzag_table0,
-    g_zigzag_table1,
-    g_zigzag_table2,
-    g_zigzag_table3,
-    g_zigzag_table4,
-    g_zigzag_table5,
-    g_zigzag_table6
-};
-
-static const uint8_t g_getid_no_disc[] = {
-    0x08, 0x40, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00
-};
-
-static const uint8_t g_getid_audio[] = {
-    0x0a, 0x90, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00
-};
-
-static const uint8_t g_getid_unlicensed[] = {
-    0x0a, 0x80, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00
-};
-
-static const uint8_t g_getid_licensed[] = {
-    0x02, 0x00, 0x20, 0x00,
-    'S' , 'C' , 'E' , 'A'
-};
-
-#define RESP_PUSH(data) { \
-    cdrom->rfifo[cdrom->rfifo_index++] = data; \
-    cdrom->rfifo_index &= 15; \
-    SET_BITS(status, STAT_RSLRRDY_MASK, STAT_RSLRRDY_MASK); }
-
-#define PFIFO_POP (cdrom->pfifo[--cdrom->pfifo_index])
-
-#define VALID_BCD(v) (((v & 0xf) <= 9) && ((v & 0xf0) <= 0x90))
-
-void cdrom_cmd_error(psx_cdrom_t* cdrom) {
-    SET_BITS(ifr, IFR_INT, IFR_INT5);
-    RESP_PUSH(cdrom->error);
-    RESP_PUSH(GETSTAT_MOTOR | cdrom->error_flags);
-
-    cdrom->pfifo_index = 0;
-    cdrom->delayed_command = CDL_NONE;
-    cdrom->state = CD_STATE_RECV_CMD;
-}
-void cdrom_cmd_unimplemented(psx_cdrom_t* cdrom) {
-    log_set_quiet(0);
-    log_fatal("Unimplemented CDROM command (%u)", cdrom->command);
-    log_set_quiet(1);
-
-    exit(1);
-}
-void cdrom_cmd_getstat(psx_cdrom_t* cdrom) {
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            if (cdrom->ongoing_read_command) {
-                cdrom->status |= STAT_BUSYSTS_MASK;
-                // printf("command=%02x\n", cdrom->ongoing_read_command);
-                cdrom->state = CD_STATE_SEND_RESP2;
-                cdrom->delayed_command = cdrom->ongoing_read_command;
-                cdrom->irq_delay = DELAY_1MS;
-
-                return;
-            }
-
-            if (cdrom->pfifo_index) {
-                log_fatal("CdlGetStat: Expected exactly 0 parameters");
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_PCOUNT;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_GETSTAT;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(
-                GETSTAT_MOTOR |
-                (cdrom->cdda_playing ? GETSTAT_PLAY : 0) |
-                // (cdrom->ongoing_read_command ? GETSTAT_READ : 0) |
-                (cdrom->disc ? 0 : GETSTAT_TRAYOPEN)
-            );
-
-            if (cdrom->ongoing_read_command) {
-                cdrom->status |= STAT_BUSYSTS_MASK;
-                // printf("command=%02x\n", cdrom->ongoing_read_command);
-                cdrom->state = CD_STATE_SEND_RESP2;
-                cdrom->delayed_command = cdrom->ongoing_read_command;
-                cdrom->irq_delay = DELAY_1MS;
-            } else {
-                cdrom->delayed_command = CDL_NONE;
-                cdrom->state = CD_STATE_RECV_CMD;
-            }
-        } break;
-    }
-}
-void cdrom_cmd_setloc(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            if (cdrom->pfifo_index != 3) {
-                log_fatal("CdlSetloc: Expected exactly 3 parameters, got %u instead",
-                    cdrom->pfifo_index
-                );
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_PCOUNT;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            int f = PFIFO_POP;
-            int s = PFIFO_POP;
-            int m = PFIFO_POP;
-
-            if (!(VALID_BCD(m) && VALID_BCD(s) && VALID_BCD(f) && (f < 0x75))) {
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_INVSUBF;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            cdrom->seek_msf.m = m;
-            cdrom->seek_msf.s = s;
-            cdrom->seek_msf.f = f;
-
-            msf_from_bcd(&cdrom->seek_msf);
-
-            cdrom->cdda_msf = cdrom->seek_msf;
-
-            cdrom->seek_pending = 1;
-
-            log_fatal("setloc: %02x:%02x:%02x",
-                cdrom->seek_msf.m,
-                cdrom->seek_msf.s,
-                cdrom->seek_msf.f
-            );
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->delayed_command = CDL_SETLOC;
-            cdrom->state = CD_STATE_SEND_RESP1;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            if (cdrom->ongoing_read_command) {
-                // printf("command=%02x\n", cdrom->ongoing_read_command);
-                cdrom->state = CD_STATE_SEND_RESP2;
-                cdrom->delayed_command = cdrom->ongoing_read_command;
-                cdrom->irq_delay = DELAY_1MS;
-            } else {
-                cdrom->delayed_command = CDL_NONE;
-                cdrom->state = CD_STATE_RECV_CMD;
-            }
-        } break;
-
-        // Read ongoing
-        case CD_STATE_SEND_RESP2: {
-            int f = PFIFO_POP;
-            int s = PFIFO_POP;
-            int m = PFIFO_POP;
-
-            if (!(VALID_BCD(m) && VALID_BCD(s) && VALID_BCD(f) && (f < 0x75))) {
-                cdrom->ongoing_read_command = false;
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_INVSUBF;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            cdrom->seek_msf.m = m;
-            cdrom->seek_msf.s = s;
-            cdrom->seek_msf.f = f;
-        } break;
-    }
-}
-void cdrom_cmd_play(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            int track = 0;
-
-            if (cdrom->cdda_playing) {
-                cdrom->pfifo_index = 0;
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->state = CD_STATE_SEND_RESP1;
-                cdrom->delayed_command = CDL_PLAY;
-
-                return;
-            }
-
-            // Optional track number parameter
-            if (cdrom->pfifo_index)
-                track = PFIFO_POP;
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_PLAY;
-
-            if (track) {
-                psx_disc_get_track_addr(cdrom->disc, &cdrom->cdda_msf, track);
-
-                msf_to_bcd(&cdrom->cdda_msf);
-
-                cdrom->cdda_track = track;
-                cdrom->seek_msf.m = cdrom->cdda_msf.m;
-                cdrom->seek_msf.s = cdrom->cdda_msf.s;
-                cdrom->seek_msf.f = cdrom->cdda_msf.f;
-    
-                cdrom->seek_pending = 1;
-            }
-
-            if (cdrom->seek_pending) {
-                cdrom->seek_pending = 0;
-
-                printf("Seeked to location\n");
-
-                cdrom->cdda_msf = cdrom->seek_msf;
-
-                // Seek to that address and read sector
-                psx_disc_seek(cdrom->disc, cdrom->cdda_msf);
-                psx_disc_read_sector(cdrom->disc, cdrom->cdda_buf);
-
-                // Increment sector
-                msf_add_f(&cdrom->cdda_msf, 1);
-
-                cdrom->cdda_sector_offset = 0;
-            }
-
-            cdrom->cdda_playing = 1;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_PLAY);
-
-            cdrom->delayed_command = CDL_NONE;
-            cdrom->state = CD_STATE_RECV_CMD;
-        } break;
-    }
-}
-void cdrom_cmd_readn(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-    cdrom->ongoing_read_command = CDL_READN;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            log_fatal("CdlReadN: CD_STATE_RECV_CMD");
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_READN;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            log_fatal("CdlReadN: CD_STATE_SEND_RESP1");
-
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            if (cdrom->mode & MODE_XA_ADPCM) {
-                cdrom->xa_msf = cdrom->seek_msf;
-                cdrom->xa_playing = 1;
-
-                SET_BITS(status, STAT_ADPBUSY_MASK, STAT_ADPBUSY_MASK);
-
-                printf("Play XA-ADPCM encoded song at %02u:%02u:%02u, filter=%u, file=%02x, channel=%02x\n",
-                    cdrom->xa_msf.m,
-                    cdrom->xa_msf.s,
-                    cdrom->xa_msf.f,
-                    (cdrom->mode & MODE_XA_FILTER) != 0,
-                    cdrom->xa_file,
-                    cdrom->xa_channel
-                );
-            }
-
-            int err = psx_disc_seek(cdrom->disc, cdrom->seek_msf);
-
-            if (err) {
-                log_set_quiet(0);
-                log_fatal("CdlReadN: Out of bounds seek");
-                log_set_quiet(1);
-
-                cdrom->irq_delay = DELAY_1MS * 600;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_INVSUBF;
-                cdrom->error_flags = GETSTAT_SEEKERROR;
-
-                return;
-            }
-
-            psx_disc_read_sector(cdrom->disc, cdrom->dfifo);
-
-            int double_speed = cdrom->mode & MODE_SPEED;
-
-            cdrom->irq_delay = double_speed ? READ_DOUBLE_DELAY : READ_SINGLE_DELAY;
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_READN;
-
-            if (cdrom->spin_delay) {
-                cdrom->irq_delay += cdrom->spin_delay;
-                cdrom->spin_delay = 0;
-            }
-        } break;
-
-        case CD_STATE_SEND_RESP2: {
-            log_fatal("CdlReadS: CD_STATE_SEND_RESP2");
-
-            // Returning only non-ADPCM sectors causes desync for some
-            // reason. I'll keep returning all sectors for now
-
-            if (cdrom->mode & MODE_XA_ADPCM) {
-                // printf("ReadS fetching non ADPCM sector...\n");
-                cdrom_fetch_video_sector(cdrom);
-
-                printf("%02u:%02u:%02u - file=%02x channel=%02x sm=%02x ci=%02x\n",
-                    cdrom->seek_msf.m,
-                    cdrom->seek_msf.s,
-                    cdrom->seek_msf.f,
-                    cdrom->dfifo[0x10],
-                    cdrom->dfifo[0x11],
-                    cdrom->dfifo[0x12],
-                    cdrom->dfifo[0x13]
-                );
-            } else {
-                psx_disc_seek(cdrom->disc, cdrom->seek_msf);
-                psx_disc_read_sector(cdrom->disc, cdrom->dfifo);
-
-                msf_add_f(&cdrom->seek_msf, 1);
-            }
-
-            int double_speed = cdrom->mode & MODE_SPEED;
-
-            cdrom->irq_delay = double_speed ? READ_DOUBLE_DELAY : READ_SINGLE_DELAY;
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_READN;
-            cdrom->dfifo_index = 0;
-
-            SET_BITS(ifr, IFR_INT, IFR_INT1);
-            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_READ);
-        } break;
-    }
-}
-void cdrom_cmd_motoron(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            cdrom->ongoing_read_command = CDL_NONE;
-            cdrom->cdda_playing = 0;
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_MOTORON;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, 3);
-            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_READ);
-
-            int double_speed = cdrom->mode & MODE_SPEED;
-
-            cdrom->irq_delay = DELAY_1MS * (double_speed ? 70 : 65);
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_MOTORON;
-        } break;
-
-        case CD_STATE_SEND_RESP2: {
-            SET_BITS(ifr, IFR_INT, 2);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->state = CD_STATE_RECV_CMD;
-            cdrom->delayed_command = CDL_NONE;
-        } break;
-    }
-}
-void cdrom_cmd_stop(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            cdrom->ongoing_read_command = CDL_NONE;
-            cdrom->cdda_playing = 0;
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_STOP;
-            cdrom->seek_msf.m = 0;
-            cdrom->seek_msf.s = 0;
-            cdrom->seek_msf.f = 0;
-
-            cdrom->cdda_msf = cdrom->seek_msf;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, 3);
-            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_READ);
-
-            int double_speed = cdrom->mode & MODE_SPEED;
-
-            cdrom->irq_delay = DELAY_1MS * (double_speed ? 70 : 65);
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_STOP;
-        } break;
-
-        case CD_STATE_SEND_RESP2: {
-            SET_BITS(ifr, IFR_INT, 2);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->state = CD_STATE_RECV_CMD;
-            cdrom->delayed_command = CDL_NONE;
-        } break;
-    }
-}
-void cdrom_cmd_pause(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            cdrom->ongoing_read_command = CDL_NONE;
-            cdrom->cdda_playing = 0;
-            cdrom->xa_playing = 0;
-
-            SET_BITS(status, STAT_ADPBUSY_MASK, 0);
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_PAUSE;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, 3);
-            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_READ);
-
-            int double_speed = cdrom->mode & MODE_SPEED;
-
-            cdrom->irq_delay = DELAY_1MS * (double_speed ? 70 : 65);
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_PAUSE;
-        } break;
-
-        case CD_STATE_SEND_RESP2: {
-            SET_BITS(ifr, IFR_INT, 2);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->state = CD_STATE_RECV_CMD;
-            cdrom->delayed_command = CDL_NONE;
-        } break;
-    }
-}
-void cdrom_cmd_init(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_INIT;
-            cdrom->ongoing_read_command = CDL_NONE;
-            cdrom->mode = 0;
-            cdrom->dfifo_index = 0;
-            cdrom->dfifo_full = 0;
-            cdrom->pfifo_index = 0;
-            cdrom->rfifo_index = 0;
-            cdrom->seek_msf.m = 0;
-            cdrom->seek_msf.s = 2;
-            cdrom->seek_msf.f = 0;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, 3);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_INIT;
-        } break;
-
-        case CD_STATE_SEND_RESP2: {
-            SET_BITS(ifr, IFR_INT, 2);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->state = CD_STATE_RECV_CMD;
-            cdrom->delayed_command = CDL_NONE;
-        } break;
-    }
-}
-void cdrom_cmd_unmute(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            if (cdrom->pfifo_index) {
-                log_fatal("CdlUnmute: Expected exactly 0 parameters");
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_PCOUNT;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->delayed_command = CDL_DEMUTE;
-            cdrom->state = CD_STATE_SEND_RESP1;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(cdrom->stat);
-
-            if (cdrom->ongoing_read_command) {
-                // printf("command=%02x\n", cdrom->ongoing_read_command);
-                cdrom->state = CD_STATE_SEND_RESP2;
-                cdrom->delayed_command = cdrom->ongoing_read_command;
-                cdrom->irq_delay = DELAY_1MS;
-            } else {
-                cdrom->delayed_command = CDL_NONE;
-                cdrom->state = CD_STATE_RECV_CMD;
-            }
-        } break;
-    }
-}
-void cdrom_cmd_setfilter(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            if (cdrom->pfifo_index != 2) {
-                log_fatal("CdlSetfilter: Expected exactly 2 parameter");
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_PCOUNT;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            cdrom->xa_channel = PFIFO_POP;
-            cdrom->xa_file = PFIFO_POP;
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->delayed_command = CDL_SETFILTER;
-            cdrom->state = CD_STATE_SEND_RESP1;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            cdrom->delayed_command = CDL_NONE;
-    
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(cdrom->stat);
-
-            if (cdrom->ongoing_read_command) {
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = cdrom->ongoing_read_command;
-                cdrom->state = CD_STATE_SEND_RESP2;
-            }
-
-            cdrom->state = CD_STATE_RECV_CMD;
-        } break;
-    }
-}
-void cdrom_cmd_setmode(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    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);
-
-            if (cdrom->ongoing_read_command) {
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = cdrom->ongoing_read_command;
-                cdrom->state = CD_STATE_SEND_RESP2;
-            }
-
-            cdrom->state = CD_STATE_RECV_CMD;
-        } break;
-    }
-}
-void cdrom_cmd_getparam(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->delayed_command = CDL_GETPARAM;
-            cdrom->state = CD_STATE_SEND_RESP1;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            cdrom->delayed_command = CDL_NONE;
-    
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(cdrom->xa_channel);
-            RESP_PUSH(cdrom->xa_file);
-            RESP_PUSH(0x00);
-            RESP_PUSH(cdrom->mode);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->state = CD_STATE_RECV_CMD;
-        } break;
-    }
-}
-void cdrom_cmd_getlocl(psx_cdrom_t* cdrom) {
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->delayed_command = CDL_GETLOCL;
-            cdrom->state = CD_STATE_SEND_RESP1;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(cdrom->dfifo[0x13]);
-            RESP_PUSH(cdrom->dfifo[0x12]);
-            RESP_PUSH(cdrom->dfifo[0x11]);
-            RESP_PUSH(cdrom->dfifo[0x10]);
-            RESP_PUSH(cdrom->dfifo[0x0f]);
-            RESP_PUSH(cdrom->dfifo[0x0e]);
-            RESP_PUSH(cdrom->dfifo[0x0d]);
-            RESP_PUSH(cdrom->dfifo[0x0c]);
-
-            // if (cdrom->ongoing_read_command) {
-            //     // printf("command=%02x\n", cdrom->ongoing_read_command);
-            //     cdrom->state = CD_STATE_SEND_RESP2;
-            //     cdrom->delayed_command = cdrom->ongoing_read_command;
-            //     cdrom->irq_delay = DELAY_1MS;
-            // } else {
-                cdrom->delayed_command = CDL_NONE;
-                cdrom->state = CD_STATE_RECV_CMD;
-            // }
-        } break;
-    }
-}
-void cdrom_cmd_getlocp(psx_cdrom_t* cdrom) {
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->delayed_command = CDL_GETLOCP;
-            cdrom->state = CD_STATE_SEND_RESP1;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            msf_t absolute = cdrom->xa_playing ? cdrom->xa_msf : cdrom->seek_msf;
-            msf_t relative = absolute;
-            msf_t track_msf = cdrom_get_track_addr(cdrom, absolute);
-
-            relative.m -= track_msf.m;
-            relative.s -= track_msf.s;
-            relative.f -= track_msf.f;
-
-            msf_adjust_sub(&relative);
-
-            // printf("abs=%02u:%02u:%02u tra=%02u:%02u:%02u rel=%02u:%02u:%02u\n",
-            //     absolute.m,
-            //     absolute.s,
-            //     absolute.f,
-            //     track_msf.m,
-            //     track_msf.s,
-            //     track_msf.f,
-            //     relative.m,
-            //     relative.s,
-            //     relative.f
-            // );
-
-            msf_to_bcd(&absolute);
-            msf_to_bcd(&relative);
-
-            // printf("getlocp 01 01 %02x:%02x:%02x %02x:%02x:%02x\n",
-            //     relative.m,
-            //     relative.s,
-            //     relative.f,
-            //     absolute.m,
-            //     absolute.s,
-            //     absolute.f
-            // );
-
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(absolute.f);
-            RESP_PUSH(absolute.s);
-            RESP_PUSH(absolute.m);
-            RESP_PUSH(relative.f);
-            RESP_PUSH(relative.s);
-            RESP_PUSH(relative.m);
-            RESP_PUSH(0x01);
-            RESP_PUSH(0x01);
-
-            if (cdrom->ongoing_read_command) {
-                //// printf("command=%02x\n", cdrom->ongoing_read_command);
-                cdrom->state = CD_STATE_SEND_RESP2;
-                cdrom->delayed_command = cdrom->ongoing_read_command;
-                cdrom->irq_delay = DELAY_1MS;
-            } else {
-                cdrom->delayed_command = CDL_NONE;
-                cdrom->state = CD_STATE_RECV_CMD;
-            }
-        } break;
-    }
-}
-void cdrom_cmd_setsession(psx_cdrom_t* cdrom) {
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->delayed_command = CDL_SETSESSION;
-            cdrom->state = CD_STATE_SEND_RESP1;
-
-            cdrom->pfifo_index = 0;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(GETSTAT_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;
-
-    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 * 2;
-            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 1 parameter");
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_PCOUNT;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            cdrom->gettd_track = PFIFO_POP;
-
-            if (!VALID_BCD(cdrom->gettd_track)) {
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_INVSUBF;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            int err = psx_disc_get_track_addr(cdrom->disc, NULL, cdrom->gettd_track);
-
-            if (err) {
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_INVSUBF;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->delayed_command = CDL_GETTD;
-            cdrom->state = CD_STATE_SEND_RESP1;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            msf_t td;
-
-            psx_disc_get_track_addr(cdrom->disc, &td, cdrom->gettd_track);
-
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(ITOB(td.s));
-            RESP_PUSH(ITOB(td.m));
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->delayed_command = CDL_NONE;
-            cdrom->state = CD_STATE_RECV_CMD;
-        } break;
-    }
-}
-void cdrom_cmd_seekl(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            if (cdrom->pfifo_index) {
-                log_fatal("CdlSeekL: Expected exactly 0 parameters");
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_PCOUNT;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_SEEKL;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, 3);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_SEEKL;
-
-            psx_disc_seek(cdrom->disc, cdrom->seek_msf);
-
-            cdrom->seek_pending = 0;
-        } break;
-
-        case CD_STATE_SEND_RESP2: {
-            SET_BITS(ifr, IFR_INT, 2);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->state = CD_STATE_RECV_CMD;
-            cdrom->delayed_command = CDL_NONE;
-        } break;
-    }
-}
-void cdrom_cmd_seekp(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            if (cdrom->pfifo_index) {
-                log_fatal("CdlSeekP: Expected exactly 0 parameters");
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_PCOUNT;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_SEEKP;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, 3);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_SEEKP;
-
-            psx_disc_seek(cdrom->disc, cdrom->seek_msf);
-
-            cdrom->seek_pending = 0;
-        } break;
-
-        case CD_STATE_SEND_RESP2: {
-            SET_BITS(ifr, IFR_INT, 2);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->state = CD_STATE_RECV_CMD;
-            cdrom->delayed_command = CDL_NONE;
-        } break;
-    }
-}
-void cdrom_cmd_test(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            if (cdrom->pfifo_index != 1) {
-                log_fatal("CdlTest: Expected exactly 1 parameter");
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_PCOUNT;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            if (PFIFO_POP != 0x20) {
-                log_fatal("CdlTest: Unhandled subcommand");
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_INVSUBF;
-
-                return;
-            }
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->delayed_command = CDL_TEST;
-            cdrom->state = CD_STATE_SEND_RESP1;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            cdrom->delayed_command = CDL_NONE;
-
-            // 95h,05h,16h,C1h
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(0xc1);
-            RESP_PUSH(0x16);
-            RESP_PUSH(0x05);
-            RESP_PUSH(0x95);
-
-            cdrom->state = CD_STATE_RECV_CMD;
-        } break;
-    }
-}
-void cdrom_cmd_getid(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            if (cdrom->pfifo_index) {
-                log_fatal("CdlGetID: Expected exactly 0 parameters");
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_PCOUNT;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_GETID;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, 3);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_GETID;
-        } break;
-
-        case CD_STATE_SEND_RESP2: {
-            if (cdrom->disc) {
-                SET_BITS(ifr, IFR_INT, 2);
-
-                switch (cdrom->cd_type) {
-                    case CDT_LICENSED: {
-                        for (int i = 0; i < GETID_RESPONSE_SIZE; i++)
-                            RESP_PUSH(g_getid_licensed[GETID_RESPONSE_END - i]);
-                    } break;
-
-                    case CDT_AUDIO: {
-                        for (int i = 0; i < GETID_RESPONSE_SIZE; i++)
-                            RESP_PUSH(g_getid_audio[GETID_RESPONSE_END - i]);
-                    } break;
-
-                    case CDT_UNKNOWN: {
-                        for (int i = 0; i < GETID_RESPONSE_SIZE; i++)
-                            RESP_PUSH(g_getid_unlicensed[GETID_RESPONSE_END - i]);
-                    } break;
-                }
-            } else {
-                SET_BITS(ifr, IFR_INT, 5);
-
-                for (int i = 0; i < GETID_RESPONSE_SIZE; i++)
-                    RESP_PUSH(g_getid_no_disc[GETID_RESPONSE_END - i]);
-            }
-
-            cdrom->state = CD_STATE_RECV_CMD;
-            cdrom->delayed_command = CDL_NONE;
-        } break;
-    }
-}
-void cdrom_cmd_reads(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-    cdrom->ongoing_read_command = CDL_READS;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            log_fatal("CdlReadS: CD_STATE_RECV_CMD");
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_READS;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            printf("CdlReadS: CD_STATE_SEND_RESP1\n");
-            log_fatal("CdlReadS: CD_STATE_SEND_RESP1");
-
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            if (cdrom->mode & MODE_XA_ADPCM) {
-                cdrom->xa_msf = cdrom->seek_msf;
-                cdrom->xa_playing = 1;
-
-                SET_BITS(status, STAT_ADPBUSY_MASK, STAT_ADPBUSY_MASK);
-
-                printf("Play XA-ADPCM encoded song at %02u:%02u:%02u, filter=%u, file=%02x, channel=%02x (ReadS)\n",
-                    cdrom->xa_msf.m,
-                    cdrom->xa_msf.s,
-                    cdrom->xa_msf.f,
-                    (cdrom->mode & MODE_XA_FILTER) != 0,
-                    cdrom->xa_file,
-                    cdrom->xa_channel
-                );
-
-                int double_speed = cdrom->mode & MODE_SPEED;
-
-                cdrom->irq_delay = DELAY_1MS * 2;
-                cdrom->state = CD_STATE_SEND_RESP2;
-                cdrom->delayed_command = CDL_READS;
-
-                // if (cdrom->spin_delay) {
-                //     cdrom->irq_delay += cdrom->spin_delay;
-                //     cdrom->spin_delay = 0;
-                // }
-
-                return;
-            }
-
-            int err = psx_disc_seek(cdrom->disc, cdrom->seek_msf);
-
-            if (err) {
-                log_fatal("CdlReadS: Out of bounds seek");
-
-                cdrom->irq_delay = DELAY_1MS * 600;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_INVSUBF;
-                cdrom->error_flags = GETSTAT_SEEKERROR;
-
-                return;
-            }
-
-            int double_speed = cdrom->mode & MODE_SPEED;
-
-            cdrom->irq_delay = double_speed ? READ_DOUBLE_DELAY : READ_SINGLE_DELAY;
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_READS;
-
-            if (cdrom->spin_delay) {
-                cdrom->irq_delay += cdrom->spin_delay;
-                cdrom->spin_delay = 0;
-            }
-        } break;
-
-        case CD_STATE_SEND_RESP2: {
-            log_fatal("CdlReadS: CD_STATE_SEND_RESP2");
-
-            // Returning only non-ADPCM sectors causes desync for some
-            // reason. I'll keep returning all sectors for now
-
-            if (cdrom->mode & MODE_XA_ADPCM) {
-                // printf("ReadS fetching non ADPCM sector...\n");
-                cdrom_fetch_video_sector(cdrom);
-
-                // printf("%02u:%02u:%02u - file=%02x channel=%02x sm=%02x ci=%02x\n",
-                //     cdrom->seek_msf.m,
-                //     cdrom->seek_msf.s,
-                //     cdrom->seek_msf.f,
-                //     cdrom->dfifo[0x10],
-                //     cdrom->dfifo[0x11],
-                //     cdrom->dfifo[0x12],
-                //     cdrom->dfifo[0x13]
-                // );
-            } else {
-                psx_disc_seek(cdrom->disc, cdrom->seek_msf);
-                psx_disc_read_sector(cdrom->disc, cdrom->dfifo);
-
-                msf_add_f(&cdrom->seek_msf, 1);
-            }
-
-            int double_speed = cdrom->mode & MODE_SPEED;
-
-            cdrom->irq_delay = DELAY_1MS * 2;
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_READS;
-            cdrom->dfifo_index = 0;
-
-            SET_BITS(ifr, IFR_INT, IFR_INT1);
-            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_READ);
-        } break;
-    }
-}
-void cdrom_cmd_readtoc(psx_cdrom_t* cdrom) {
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            cdrom->status |= STAT_BUSYSTS_MASK;
-            cdrom->irq_delay = DELAY_1MS * 1000;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_READTOC;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            cdrom->status &= ~STAT_BUSYSTS_MASK;
-            SET_BITS(ifr, IFR_INT, 3);
-            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_READ);
-
-            cdrom->irq_delay = DELAY_1MS * 1000;
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_READTOC;
-        } break;
-
-        case CD_STATE_SEND_RESP2: {
-            SET_BITS(ifr, IFR_INT, 2);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->state = CD_STATE_RECV_CMD;
-            cdrom->delayed_command = CDL_NONE;
-        } break;
-    }
-}
-void cdrom_cmd_videocd(psx_cdrom_t* cdrom) {
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_VIDEOCD;
-            cdrom->pfifo_index = 0;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            printf("VideoCD task %02x\n", cdrom->pfifo[4]);
-            SET_BITS(ifr, IFR_INT, 3);
-
-            switch (cdrom->pfifo[4]) {
-                case 0: {
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(GETSTAT_MOTOR);
-                } break;
-
-                case 1: {
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x81);
-                    RESP_PUSH(GETSTAT_MOTOR);
-                } break;
-
-                case 2: {
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x05);
-                    RESP_PUSH(GETSTAT_MOTOR);
-                } break;
-            }
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_RECV_CMD;
-            cdrom->delayed_command = CDL_NONE;
-        } break;
-    }
-}
-
-typedef void (*cdrom_cmd_t)(psx_cdrom_t*);
-
-const char* g_psx_cdrom_command_names[] = {
-    "CdlUnimplemented",
-    "CdlGetstat",
-    "CdlSetloc",
-    "CdlPlay",
-    "CdlUnimplemented",
-    "CdlUnimplemented",
-    "CdlReadn",
-    "CdlMotoron",
-    "CdlStop",
-    "CdlPause",
-    "CdlInit",
-    "CdlUnimplemented",
-    "CdlUnmute",
-    "CdlSetfilter",
-    "CdlSetmode",
-    "CdlGetparam",
-    "CdlGetlocl",
-    "CdlGetlocp",
-    "CdlSetsession",
-    "CdlGettn",
-    "CdlGettd",
-    "CdlSeekl",
-    "CdlSeekp",
-    "CdlUnimplemented",
-    "CdlUnimplemented",
-    "CdlTest",
-    "CdlGetid",
-    "CdlReads",
-    "CdlUnimplemented",
-    "CdlUnimplemented",
-    "CdlReadtoc",
-    "CdlVideoCD"
-};
-
-cdrom_cmd_t g_psx_cdrom_command_table[] = {
-    cdrom_cmd_unimplemented,
-    cdrom_cmd_getstat,
-    cdrom_cmd_setloc,
-    cdrom_cmd_play,
-    cdrom_cmd_unimplemented,
-    cdrom_cmd_unimplemented,
-    cdrom_cmd_readn,
-    cdrom_cmd_motoron,
-    cdrom_cmd_stop,
-    cdrom_cmd_pause,
-    cdrom_cmd_init,
-    cdrom_cmd_unmute,
-    cdrom_cmd_unmute,
-    cdrom_cmd_setfilter,
-    cdrom_cmd_setmode,
-    cdrom_cmd_getparam,
-    cdrom_cmd_getlocl,
-    cdrom_cmd_getlocp,
-    cdrom_cmd_setsession,
-    cdrom_cmd_gettn,
-    cdrom_cmd_gettd,
-    cdrom_cmd_seekl,
-    cdrom_cmd_seekp,
-    cdrom_cmd_unimplemented,
-    cdrom_cmd_unimplemented,
-    cdrom_cmd_test,
-    cdrom_cmd_getid,
-    cdrom_cmd_reads,
-    cdrom_cmd_unimplemented,
-    cdrom_cmd_unimplemented,
-    cdrom_cmd_readtoc,
-    cdrom_cmd_videocd,
-
-    // Actually an unimplemented command, we use this
-    // index for CD error handling
-    cdrom_cmd_error
-};
-
-typedef uint8_t (*psx_cdrom_read_function_t)(psx_cdrom_t*);
-typedef void (*psx_cdrom_write_function_t)(psx_cdrom_t*, uint8_t);
-
-uint8_t cdrom_read_status(psx_cdrom_t* cdrom) {
-    return cdrom->status;
-}
-
-uint8_t cdrom_read_rfifo(psx_cdrom_t* cdrom) {
-    uint8_t data = cdrom->rfifo[--cdrom->rfifo_index];
-
-    if (cdrom->rfifo_index == 0)
-        SET_BITS(status, STAT_RSLRRDY_MASK, 0);
-    
-    return data;
-}
-
-uint8_t cdrom_read_dfifo(psx_cdrom_t* cdrom) {
-    if (!cdrom->dfifo_full)
-        return 0;
-
-    int sector_size_bit = cdrom->mode & MODE_SECTOR_SIZE;
-
-    uint32_t sector_size = sector_size_bit ? 0x924 : 0x800;
-    uint32_t offset = sector_size_bit ? 12 : 24;
-
-    if (cdrom->dfifo_index < sector_size) {
-        SET_BITS(status, STAT_DRQSTS_MASK, STAT_DRQSTS_MASK);
-
-        uint8_t data = cdrom->dfifo[offset + (cdrom->dfifo_index++)];
-
-        if (cdrom->dfifo_index >= sector_size)
-            SET_BITS(status, STAT_DRQSTS_MASK, 0);
-
-        return data;
-    }
-
-    return 0;
-}
-
-uint8_t cdrom_read_ier(psx_cdrom_t* cdrom) {
-    return cdrom->ier;
-}
-
-uint8_t cdrom_read_ifr(psx_cdrom_t* cdrom) {
-    return 0xe0 | cdrom->ifr;
-}
-
-void cdrom_write_status(psx_cdrom_t* cdrom, uint8_t value) {
-    SET_BITS(status, STAT_INDEX_MASK, value);
-}
-
-void cdrom_write_cmd(psx_cdrom_t* cdrom, uint8_t value) {
-    // printf("%s(%02x) %u params=[%02x, %02x, %02x, %02x, %02x, %02x]\n",
-    //     g_psx_cdrom_command_names[value],
-    //     value,
-    //     cdrom->pfifo_index,
-    //     cdrom->pfifo[0],
-    //     cdrom->pfifo[1],
-    //     cdrom->pfifo[2],
-    //     cdrom->pfifo[3],
-    //     cdrom->pfifo[4],
-    //     cdrom->pfifo[5]
-    // );
-
-    cdrom->command = value;
-    cdrom->state = CD_STATE_RECV_CMD;
-
-    g_psx_cdrom_command_table[value](cdrom);
-}
-
-void cdrom_write_pfifo(psx_cdrom_t* cdrom, uint8_t value) {
-    cdrom->pfifo[(cdrom->pfifo_index++) & 0xf] = value;
-
-    SET_BITS(status, STAT_PRMWRDY_MASK, (cdrom->pfifo_index & 0x10) ? 0x0 : 0xff);
-
-    cdrom->pfifo_index &= 0x1f;
-}
-
-void cdrom_write_req(psx_cdrom_t* cdrom, uint8_t value) {
-    if (value & REQ_BFRD) {
-        SET_BITS(status, STAT_DRQSTS_MASK, STAT_DRQSTS_MASK);
-
-        cdrom->dfifo_full = 1;
-    } else {
-        SET_BITS(status, STAT_DRQSTS_MASK, 0);
-
-        cdrom->dfifo_full = 0;
-    }
-}
-
-void cdrom_write_smdout(psx_cdrom_t* cdrom, uint8_t value) {
-    log_fatal("Sound map data out unimplemented");
-}
-
-void cdrom_write_ier(psx_cdrom_t* cdrom, uint8_t value) {
-    cdrom->ier = value;
-}
-
-void cdrom_write_ifr(psx_cdrom_t* cdrom, uint8_t value) {
-    cdrom->ifr &= ~(value & 0x1f);
-
-    // Clear Parameter FIFO
-    if (value & 0x40) {
-        cdrom->pfifo_index = 0;
-
-        SET_BITS(
-            status,
-            (STAT_PRMEMPT_MASK | STAT_PRMWRDY_MASK),
-            (STAT_PRMEMPT_MASK | STAT_PRMWRDY_MASK)
-        );
-    }
-}
-
-void cdrom_write_sminfo(psx_cdrom_t* cdrom, uint8_t value) {
-    log_fatal("Sound map coding info unimplemented");
-}
-
-void cdrom_write_lcdlspuv(psx_cdrom_t* cdrom, uint8_t value) {
-    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));
-}
-
-#define XA_STEREO_SAMPLES 2016
-#define XA_MONO_SAMPLES 4032
-#define XA_DECODED_SAMPLES 37632
-#define XA_RINGBUF_SIZE 32
-#define XA_STEREO_RESAMPLE_SIZE 2352
-#define XA_MONO_RESAMPLE_SIZE 4704
-
-void psx_cdrom_init(psx_cdrom_t* cdrom, psx_ic_t* ic) {
-    memset(cdrom, 0, sizeof(psx_cdrom_t));
-
-    cdrom->io_base = PSX_CDROM_BEGIN;
-    cdrom->io_size = PSX_CDROM_SIZE;
-
-    cdrom->ic = ic;
-    cdrom->status = STAT_PRMEMPT_MASK | STAT_PRMWRDY_MASK | STAT_RSLRRDY_MASK;
-    cdrom->dfifo = malloc(CD_SECTOR_SIZE);
-    cdrom->cdda_buf = malloc(CD_SECTOR_SIZE);
-
-    // Initialize XA state
-    cdrom->xa_sector_buf = malloc(CD_SECTOR_SIZE);
-    cdrom->xa_left_buf = malloc(XA_STEREO_SAMPLES * sizeof(int16_t));
-    cdrom->xa_right_buf = malloc(XA_STEREO_SAMPLES * sizeof(int16_t));
-    cdrom->xa_mono_buf = malloc(XA_MONO_SAMPLES * sizeof(int16_t));
-    cdrom->xa_upsample_buf = malloc(((14112 * 2) + 6) * sizeof(int16_t));
-    cdrom->xa_left_resample_buf = malloc((XA_STEREO_RESAMPLE_SIZE * 2) * sizeof(int16_t));
-    cdrom->xa_right_resample_buf = malloc((XA_STEREO_RESAMPLE_SIZE * 2) * sizeof(int16_t));
-    cdrom->xa_mono_resample_buf = malloc((XA_MONO_RESAMPLE_SIZE * 2) * sizeof(int16_t));
-    cdrom->xa_step = 6;
-
-    // We will use this whenever we implement proper
-    // XA interpolation
-    (void)g_zigzag_table;
-
-    memset(cdrom->xa_left_buf, 0, XA_STEREO_SAMPLES * sizeof(int16_t));
-    memset(cdrom->xa_right_buf, 0, XA_STEREO_SAMPLES * sizeof(int16_t));
-    memset(cdrom->xa_mono_buf, 0, XA_MONO_SAMPLES * sizeof(int16_t));
-    memset(cdrom->xa_upsample_buf, 0, ((14112 * 2) + 6) * sizeof(int16_t));
-    memset(cdrom->xa_left_resample_buf, 0, (XA_STEREO_RESAMPLE_SIZE * 2) * sizeof(int16_t));
-    memset(cdrom->xa_right_resample_buf, 0, (XA_STEREO_RESAMPLE_SIZE * 2) * sizeof(int16_t));
-    memset(cdrom->xa_mono_resample_buf, 0, (XA_MONO_RESAMPLE_SIZE * 2) * sizeof(int16_t));
-
-    cdrom->seek_msf.m = 0;
-    cdrom->seek_msf.s = 2;
-    cdrom->seek_msf.f = 0;
-}
-
-uint32_t psx_cdrom_read32(psx_cdrom_t* cdrom, uint32_t offset) {
-    log_set_quiet(0);
-    log_fatal("Unhandled 32-bit CDROM read at offset %08x", offset);
-
-    // exit(1);
-
-    return 0x0;
-}
-
-uint16_t psx_cdrom_read16(psx_cdrom_t* cdrom, uint32_t offset) {
-    log_set_quiet(0);
-    log_fatal("Unhandled 16-bit CDROM read at offset %08x", offset);
-
-    // exit(1);
-
-    return 0x0;
-}
-
-uint8_t psx_cdrom_read8(psx_cdrom_t* cdrom, uint32_t offset) {
-    uint8_t data = g_psx_cdrom_read_table[(STAT_INDEX << 2) | offset](cdrom);
-
-    if (((STAT_INDEX << 2) | offset) == 2)
-        return data;
-
-    // log_set_quiet(0);
-    // log_fatal("%s (read %02x)", g_psx_cdrom_read_names_table[(STAT_INDEX << 2) | offset], data);
-    // log_set_quiet(1);
-
-    return data;
-}
-
-void psx_cdrom_write32(psx_cdrom_t* cdrom, uint32_t offset, uint32_t value) {
-    log_set_quiet(0);
-    log_fatal("Unhandled 32-bit CDROM write at offset %08x (%08x)", offset, value);
-
-    // exit(1);
-}
-
-void psx_cdrom_write16(psx_cdrom_t* cdrom, uint32_t offset, uint16_t value) {
-    log_set_quiet(0);
-    log_fatal("Unhandled 16-bit CDROM write at offset %08x (%04x)", offset, value);
-
-    // exit(1);
-}
-
-void psx_cdrom_write8(psx_cdrom_t* cdrom, uint32_t offset, uint8_t value) {
-    // log_set_quiet(0);
-    // log_fatal("%s (write %02x)", g_psx_cdrom_write_names_table[(STAT_INDEX << 2) | offset], value);
-    // log_set_quiet(1);
-
-    g_psx_cdrom_write_table[(STAT_INDEX << 2) | offset](cdrom, value);
-}
-
-void psx_cdrom_update(psx_cdrom_t* cdrom, int cyc) {
-    if (cdrom->irq_delay) {
-        cdrom->irq_delay -= cyc;
-
-        if (cdrom->irq_delay <= 0) {
-            if (!cdrom->irq_disable) {
-                psx_ic_irq(cdrom->ic, IC_CDROM);
-            } else {
-                cdrom->irq_disable = 0;
-            }
-
-            cdrom->irq_delay = 0;
-
-            if (cdrom->delayed_command) {
-                // log_set_quiet(0);
-                // log_fatal("%s(%02x) (Delayed)",
-                //     g_psx_cdrom_command_names[cdrom->delayed_command],
-                //     cdrom->delayed_command
-                // );
-                // log_set_quiet(1);
-                g_psx_cdrom_command_table[cdrom->delayed_command](cdrom);
-            }
-
-            // log_set_quiet(0);
-            // log_fatal("CDROM INT%u", cdrom->ifr & 0x7);
-            // log_set_quiet(1);
-        }
-    }
-}
-
-const char* g_psx_cdrom_extensions[] = {
-    "cue",
-    "bin",
-    0
-};
-
-enum {
-    CD_EXT_CUE,
-    CD_EXT_BIN,
-    CD_EXT_UNSUPPORTED
-};
-
-int cdrom_get_extension(const char* path) {
-    const char* ptr = &path[strlen(path) - 1];
-    int i = 0;
-
-    while ((*ptr != '.') && (ptr != path))
-        ptr--;
-    
-    if (ptr == path)
-        return CD_EXT_UNSUPPORTED;
-
-    while (g_psx_cdrom_extensions[i]) {
-        if (!strcmp(ptr + 1, g_psx_cdrom_extensions[i]))
-            return i;
-        
-        ++i;
-    }
-
-    return CD_EXT_UNSUPPORTED;
-}
-
-void cdrom_check_cd_type(psx_cdrom_t* cdrom) {
-    char buf[CD_SECTOR_SIZE];
-
-    // Seek to Primary Volume Descriptor
-    msf_t pvd = { 0, 2, 16 };
-
-    // If the disc is smaller than 16 sectors
-    // then it can't be a PlayStation game.
-    // Audio discs should also have ISO volume
-    // descriptors, so it's probably something else
-    // entirely.
-    if (psx_disc_seek(cdrom->disc, pvd)) {
-        cdrom->cd_type = CDT_UNKNOWN;
-
-        return;
-    }
-
-    psx_disc_read_sector(cdrom->disc, buf);
-
-    // Check for the "PLAYSTATION" string at PVD offset 20h
-
-    // Patch 20 byte so comparison is done correctly
-    buf[0x2b] = 0;
-
-    if (strncmp(&buf[0x20], "PLAYSTATION", 12)) {
-        cdrom->cd_type = CDT_AUDIO;
-
-        return;
-    }
-
-    cdrom->cd_type = CDT_LICENSED;
-}
-
-void psx_cdrom_open(psx_cdrom_t* cdrom, const char* path) {
-    cdrom->disc = psx_disc_create();
-
-    int len = strlen(path);
-
-    char* lower = malloc(len + 1);
-
-    for (int i = 0; i < len; i++)
-        lower[i] = tolower(path[i]);
-
-    lower[len] = '\0';
-
-    int ext = cdrom_get_extension(lower);
-    int error = 0;
-
-    switch (ext) {
-        case CD_EXT_CUE: {
-            psxd_cue_t* cue = psxd_cue_create();
-
-            psxd_cue_init_disc(cue, cdrom->disc);
-            psxd_cue_init(cue);
-
-            error = psxd_cue_load(cue, path);
-
-            if (error)
-                break;
-
-            cdrom_check_cd_type(cdrom);
-        } break;
-
-        case CD_EXT_BIN: {
-            psxd_bin_t* bin = psxd_bin_create();
-
-            psxd_bin_init_disc(bin, cdrom->disc);
-            psxd_bin_init(bin);
-
-            error = psxd_bin_load(bin, path);
-
-            if (error)
-                break;
-
-            cdrom_check_cd_type(cdrom);
-        } break;
-
-        case CD_EXT_UNSUPPORTED: {
-            log_fatal("Unsupported disc format");
-
-            cdrom->cd_type = CDT_UNKNOWN;
-        } break;
-    }
-
-    free(lower);
-
-    if (error) {
-        log_fatal("Error loading file \'%s\'", path);
-
-        exit(1);
-    }
-}
-
-static const int g_spu_pos_adpcm_table[] = {
-    0, +60, +115, +98, +122
-};
-
-static const int g_spu_neg_adpcm_table[] = {
-    0,   0,  -52, -55,  -60
-};
-
-void cdrom_resample_xa_buf(psx_cdrom_t* cdrom, int16_t* dst, int16_t* src, int stereo) {
-    int f18khz = ((cdrom->xa_sector_buf[0x13] >> 2) & 1) == 1;
-    int sample_count = stereo ? XA_STEREO_SAMPLES : XA_MONO_SAMPLES;
-    int resample_count = stereo ? XA_STEREO_RESAMPLE_SIZE : XA_MONO_RESAMPLE_SIZE;
-
-    // Upsampling
-    for (int i = 0; i < sample_count; i++) {
-        int j = i * 7;
-
-        cdrom->xa_upsample_buf[j] = src[i];
-
-        // Nearest neighbor
-        for (int k = 0; k < 7; k++)
-            cdrom->xa_upsample_buf[j+k] = src[i];
-    }
-
-    if (f18khz) {
-        for (int i = 0; i < resample_count; i++) {
-            *dst++ = cdrom->xa_upsample_buf[i*3];
-            *dst++ = cdrom->xa_upsample_buf[i*3];
-        }
-    } else {
-        for (int i = 0; i < resample_count; i++)
-            dst[i] = cdrom->xa_upsample_buf[i*6];
-    }
-
-    cdrom->xa_remaining_samples = resample_count * (f18khz + 1);
-}
-
-void cdrom_decode_xa_block(psx_cdrom_t* cdrom, int idx, int blk, int nib, int16_t* buf, int16_t* h) {
-    int shift  = 12 - (cdrom->xa_sector_buf[idx + 4 + blk * 2 + nib] & 0x0F);
-    int filter =      (cdrom->xa_sector_buf[idx + 4 + blk * 2 + nib] & 0x30) >> 4;
-
-    int32_t f0 = g_spu_pos_adpcm_table[filter];
-    int32_t f1 = g_spu_neg_adpcm_table[filter];
-
-    for (int j = 0; j < 28; j++) {
-        uint16_t n = (cdrom->xa_sector_buf[idx + 16 + blk + j * 4] >> (nib * 4)) & 0x0f;
-
-        int16_t t = (int16_t)(n << 12) >> 12; 
-        int16_t s = (t << shift) + (((h[0] * f0) + (h[1] * f1) + 32) / 64);
-
-        s = (s < INT16_MIN) ? INT16_MIN : ((s > INT16_MAX) ? INT16_MAX : s);
-
-        h[1] = h[0];
-        h[0] = s;
-
-        buf[j] = s;
-    }
-}
-
-void cdrom_decode_xa_sector(psx_cdrom_t* cdrom, void* buf) {
-    int src = 24;
-
-    int16_t left[28];
-    int16_t right[28];
-    int16_t left_h[2] = { 0, 0 };
-    int16_t right_h[2] = { 0, 0 };
-
-    int16_t* left_ptr = cdrom->xa_left_buf;
-    int16_t* right_ptr = cdrom->xa_right_buf;
-    int16_t* mono_ptr = cdrom->xa_mono_buf;
-
-    for (int i = 0; i < 18; i++) {
-        for (int blk = 0; blk < 4; blk++) {
-            if (cdrom->xa_sector_buf[0x13] & 1) {
-                cdrom_decode_xa_block(cdrom, src, blk, 0, left, 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;
-    }
-}
-
-void cdrom_fetch_xa_sector(psx_cdrom_t* cdrom) {
-    while (true) {
-        if (psx_disc_seek(cdrom->disc, cdrom->xa_msf)) {
-            cdrom->xa_playing = 0;
-
-            return;
-        }
-
-        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) {
-        int16_t* ptr = (int16_t*)buf;
-
-        for (int i = 0; i < (size >> 2); i++) {
-            int stereo = (cdrom->xa_sector_buf[0x13] & 1) == 1;
-
-            if (!cdrom->xa_remaining_samples) {
-                cdrom_fetch_xa_sector(cdrom);
-
-                if (cdrom->xa_sector_buf[0x12] & 0x80) {
-                    SET_BITS(status, STAT_ADPBUSY_MASK, 0);
-
-                    cdrom->xa_playing = 0;
-                    cdrom->xa_remaining_samples = 0;
-
-                    return;
-                }
-
-                stereo = (cdrom->xa_sector_buf[0x13] & 1) == 1;
-
-                cdrom_decode_xa_sector(cdrom, buf);
-
-                if (stereo) {
-                    cdrom_resample_xa_buf(cdrom, cdrom->xa_left_resample_buf, cdrom->xa_left_buf, stereo);
-                    cdrom_resample_xa_buf(cdrom, cdrom->xa_right_resample_buf, cdrom->xa_right_buf, stereo);
-                } else {
-                    cdrom_resample_xa_buf(cdrom, cdrom->xa_mono_resample_buf, cdrom->xa_mono_buf, stereo);
-                }
-
-                cdrom->xa_sample_idx = 0;
-            }
-
-            if (stereo) {
-                *ptr++ = cdrom->xa_left_resample_buf[(cdrom->xa_sample_idx) % XA_STEREO_RESAMPLE_SIZE];
-                *ptr++ = cdrom->xa_right_resample_buf[(cdrom->xa_sample_idx++) % XA_STEREO_RESAMPLE_SIZE];
-            } else {
-                *ptr++ = cdrom->xa_mono_resample_buf[(cdrom->xa_sample_idx) % XA_MONO_RESAMPLE_SIZE];
-                *ptr++ = cdrom->xa_mono_resample_buf[(cdrom->xa_sample_idx++) % XA_MONO_RESAMPLE_SIZE];
-            }
-
-            --cdrom->xa_remaining_samples;
-        }
-
-        return;
-    }
-
-    if (!cdrom->cdda_playing) {
-        memset(buf, 0, size);
-    
-        return;
-    }
-
-    // Seek to that address and read sector
-    if (psx_disc_seek(cdrom->disc, cdrom->cdda_msf))
-        cdrom->cdda_playing = 0;
-
-    psx_disc_read_sector(cdrom->disc, cdrom->cdda_buf);
-
-    ++cdrom->cdda_sectors_played;
-
-    // Increment sector
-    msf_add_f(&cdrom->cdda_msf, 1);
-
-    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);
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "cdrom.h"
+#include "../log.h"
+#include "../msf.h"
+
+/*
+    Drive Status           1st Response   2nd Response
+    Door Open              INT5(11h,80h)  N/A
+    Spin-up                INT5(01h,80h)  N/A
+    Detect busy            INT5(03h,80h)  N/A
+    No Disk                INT3(stat)     INT5(08h,40h, 00h,00h, 00h,00h,00h,00h)
+    Audio Disk             INT3(stat)     INT5(0Ah,90h, 00h,00h, 00h,00h,00h,00h)
+    Unlicensed:Mode1       INT3(stat)     INT5(0Ah,80h, 00h,00h, 00h,00h,00h,00h)
+    Unlicensed:Mode2       INT3(stat)     INT5(0Ah,80h, 20h,00h, 00h,00h,00h,00h)
+    Unlicensed:Mode2+Audio INT3(stat)     INT5(0Ah,90h, 20h,00h, 00h,00h,00h,00h)
+    Debug/Yaroze:Mode2     INT3(stat)     INT2(02h,00h, 20h,00h, 20h,20h,20h,20h)
+    Licensed:Mode2         INT3(stat)     INT2(02h,00h, 20h,00h, 53h,43h,45h,4xh)
+    Modchip:Audio/Mode1    INT3(stat)     INT2(02h,00h, 00h,00h, 53h,43h,45h,4xh)
+*/
+
+msf_t cdrom_get_track_addr(psx_cdrom_t* cdrom, msf_t msf) {
+    uint32_t lba = msf_to_address(msf);
+
+    int num_tracks, track;
+
+    psx_disc_get_track_count(cdrom->disc, &num_tracks);
+
+    for (track = 1; track < num_tracks - 1; track++) {
+        msf_t curr, next;
+
+        psx_disc_get_track_addr(cdrom->disc, &curr, track);
+        psx_disc_get_track_addr(cdrom->disc, &next, track + 1);
+
+        uint32_t curr_lba = msf_to_address(curr);
+        uint32_t next_lba = msf_to_address(next);
+
+        // printf("lba=%02u:%02u:%02u (%08x) curr=%02u:%02u:%02u (%08x) next=%02u:%02u:%02u (%08x)\n",
+        //     msf.m,
+        //     msf.s,
+        //     msf.f,
+        //     lba,
+        //     curr.m,
+        //     curr.s,
+        //     curr.f,
+        //     curr_lba,
+        //     next.m,
+        //     next.s,
+        //     next.f,
+        //     next_lba
+        // );
+
+        if ((lba >= curr_lba) && (lba < next_lba))
+            break;
+    }
+
+    msf_t track_msf;
+
+    psx_disc_get_track_addr(cdrom->disc, &track_msf, track);
+
+    return track_msf;
+}
+
+void cdrom_fetch_video_sector(psx_cdrom_t* cdrom) {
+    while (true) {
+        if (psx_disc_seek(cdrom->disc, cdrom->seek_msf))
+            return;
+
+        psx_disc_read_sector(cdrom->disc, cdrom->dfifo);
+
+        msf_add_f(&cdrom->seek_msf, 1);
+
+        // Check RT and Video/Data bit
+        // if (cdrom->dfifo[0x12] & 4)
+        //     continue;
+
+        // If we get here it means this is a real-time video sector.
+        // If the XA filter is disabled, we're done
+        if (!(cdrom->mode & MODE_XA_FILTER))
+            return;
+
+        // Else check XA file/channel
+        int file_eq = cdrom->dfifo[0x10] == cdrom->xa_file;
+        int channel_eq = cdrom->dfifo[0x11] == cdrom->xa_channel;
+
+        // If they are equal to our filter values, we're done
+        // else keep searching
+        if (file_eq && channel_eq)
+            return;
+    }
+}
+
+#define GETID_RESPONSE_SIZE 8
+#define GETID_RESPONSE_END (GETID_RESPONSE_SIZE - 1)
+
+static const int16_t g_zigzag_table0[] = {
+     0x0000,  0x0000,  0x0000,  0x0000,
+     0x0000, -0x0002,  0x000a, -0x0022,
+     0x0041, -0x0054,  0x0034,  0x0009,
+    -0x010a,  0x0400, -0x0a78,  0x234c,
+     0x6794, -0x1780,  0x0bcd, -0x0623,
+     0x0350, -0x016d,  0x006b,  0x000a,
+    -0x0010,  0x0011, -0x0008,  0x0003,
+    -0x0001
+};
+
+static const int16_t g_zigzag_table1[] = {
+     0x0000,  0x0000,  0x0000, -0x0002,
+     0x0000,  0x0003, -0x0013,  0x003c,
+    -0x004b,  0x00a2, -0x00e3,  0x0132,
+    -0x0043, -0x0267,  0x0c9d,  0x74bb,
+    -0x11b4,  0x09b8, -0x05bf,  0x0372,
+    -0x01a8,  0x00a6, -0x001b,  0x0005,
+     0x0006, -0x0008,  0x0003, -0x0001,
+     0x0000
+};
+
+static const int16_t g_zigzag_table2[] = {
+     0x0000,  0x0000, -0x0001,  0x0003,
+    -0x0002, -0x0005,  0x001f, -0x004a,
+     0x00b3, -0x0192,  0x02b1, -0x039e,
+     0x04f8, -0x05a6,  0x7939, -0x05a6,
+     0x04f8, -0x039e,  0x02b1, -0x0192,
+     0x00b3, -0x004a,  0x001f, -0x0005,
+    -0x0002,  0x0003, -0x0001,  0x0000,
+     0x0000
+};
+
+static const int16_t g_zigzag_table3[] = {
+     0x0000, -0x0001,  0x0003, -0x0008,
+     0x0006,  0x0005, -0x001b,  0x00a6,
+    -0x01a8,  0x0372, -0x05bf,  0x09b8,
+    -0x11b4,  0x74bb,  0x0c9d, -0x0267,
+    -0x0043,  0x0132, -0x00e3,  0x00a2,
+    -0x004b,  0x003c, -0x0013,  0x0003,
+     0x0000, -0x0002,  0x0000,  0x0000,
+     0x0000
+};
+
+static const int16_t g_zigzag_table4[] = {
+    -0x0001,  0x0003, -0x0008,  0x0011,
+    -0x0010,  0x000a,  0x006b, -0x016d,
+     0x0350, -0x0623,  0x0bcd, -0x1780,
+     0x6794,  0x234c, -0x0a78,  0x0400,
+    -0x010a,  0x0009,  0x0034, -0x0054,
+     0x0041, -0x0022,  0x000a, -0x0001,
+     0x0000,  0x0001,  0x0000,  0x0000,
+     0x0000
+};
+
+static const int16_t g_zigzag_table5[] = {
+     0x0002, -0x0008,  0x0010, -0x0023,
+     0x002b,  0x001a, -0x00eb,  0x027b,
+    -0x0548,  0x0afa, -0x16fa,  0x53e0,
+     0x3c07, -0x1249,  0x080e, -0x0347,
+     0x015b, -0x0044, -0x0017,  0x0046,
+    -0x0023,  0x0011, -0x0005,  0x0000,
+     0x0000,  0x0000,  0x0000,  0x0000,
+     0x0000
+};
+
+static const int16_t g_zigzag_table6[] = {
+    -0x0005,  0x0011, -0x0023,  0x0046,
+    -0x0017, -0x0044,  0x015b, -0x0347,
+     0x080e, -0x1249,  0x3c07,  0x53e0,
+    -0x16fa,  0x0afa, -0x0548,  0x027b,
+    -0x00eb,  0x001a,  0x002b, -0x0023,
+     0x0010, -0x0008,  0x0002,  0x0000,
+     0x0000,  0x0000,  0x0000,  0x0000,
+     0x0000
+};
+
+static const int16_t* g_zigzag_table[] = {
+    g_zigzag_table0,
+    g_zigzag_table1,
+    g_zigzag_table2,
+    g_zigzag_table3,
+    g_zigzag_table4,
+    g_zigzag_table5,
+    g_zigzag_table6
+};
+
+static const uint8_t g_getid_no_disc[] = {
+    0x08, 0x40, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00
+};
+
+static const uint8_t g_getid_audio[] = {
+    0x0a, 0x90, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00
+};
+
+static const uint8_t g_getid_unlicensed[] = {
+    0x0a, 0x80, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00
+};
+
+static const uint8_t g_getid_licensed[] = {
+    0x02, 0x00, 0x20, 0x00,
+    'S' , 'C' , 'E' , 'A'
+};
+
+#define RESP_PUSH(data) { \
+    cdrom->rfifo[cdrom->rfifo_index++] = data; \
+    cdrom->rfifo_index &= 15; \
+    SET_BITS(status, STAT_RSLRRDY_MASK, STAT_RSLRRDY_MASK); }
+
+#define PFIFO_POP (cdrom->pfifo[--cdrom->pfifo_index])
+
+#define VALID_BCD(v) (((v & 0xf) <= 9) && ((v & 0xf0) <= 0x90))
+
+void cdrom_cmd_error(psx_cdrom_t* cdrom) {
+    SET_BITS(ifr, IFR_INT, IFR_INT5);
+    RESP_PUSH(cdrom->error);
+    RESP_PUSH(GETSTAT_MOTOR | cdrom->error_flags);
+
+    cdrom->pfifo_index = 0;
+    cdrom->delayed_command = CDL_NONE;
+    cdrom->state = CD_STATE_RECV_CMD;
+}
+void cdrom_cmd_unimplemented(psx_cdrom_t* cdrom) {
+    // log_set_quiet(0);
+    log_fatal("Unimplemented CDROM command (%u)", cdrom->command);
+    log_set_quiet(1);
+
+    exit(1);
+}
+void cdrom_cmd_getstat(psx_cdrom_t* cdrom) {
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            if (cdrom->ongoing_read_command) {
+                cdrom->status |= STAT_BUSYSTS_MASK;
+                // printf("command=%02x\n", cdrom->ongoing_read_command);
+                cdrom->state = CD_STATE_SEND_RESP2;
+                cdrom->delayed_command = cdrom->ongoing_read_command;
+                cdrom->irq_delay = DELAY_1MS;
+
+                return;
+            }
+
+            if (cdrom->pfifo_index) {
+                log_fatal("CdlGetStat: Expected exactly 0 parameters");
+
+                cdrom->irq_delay = DELAY_1MS;
+                cdrom->delayed_command = CDL_ERROR;
+                cdrom->state = CD_STATE_ERROR;
+                cdrom->error = ERR_PCOUNT;
+                cdrom->error_flags = GETSTAT_ERROR;
+
+                return;
+            }
+
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->state = CD_STATE_SEND_RESP1;
+            cdrom->delayed_command = CDL_GETSTAT;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            SET_BITS(ifr, IFR_INT, IFR_INT3);
+            RESP_PUSH(
+                GETSTAT_MOTOR |
+                (cdrom->cdda_playing ? GETSTAT_PLAY : 0) |
+                // (cdrom->ongoing_read_command ? GETSTAT_READ : 0) |
+                (cdrom->disc ? 0 : GETSTAT_TRAYOPEN)
+            );
+
+            if (cdrom->ongoing_read_command) {
+                cdrom->status |= STAT_BUSYSTS_MASK;
+                // printf("command=%02x\n", cdrom->ongoing_read_command);
+                cdrom->state = CD_STATE_SEND_RESP2;
+                cdrom->delayed_command = cdrom->ongoing_read_command;
+                cdrom->irq_delay = DELAY_1MS;
+            } else {
+                cdrom->delayed_command = CDL_NONE;
+                cdrom->state = CD_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+void cdrom_cmd_setloc(psx_cdrom_t* cdrom) {
+    cdrom->delayed_command = CDL_NONE;
+
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            if (cdrom->pfifo_index != 3) {
+                log_fatal("CdlSetloc: Expected exactly 3 parameters, got %u instead",
+                    cdrom->pfifo_index
+                );
+
+                cdrom->irq_delay = DELAY_1MS;
+                cdrom->delayed_command = CDL_ERROR;
+                cdrom->state = CD_STATE_ERROR;
+                cdrom->error = ERR_PCOUNT;
+                cdrom->error_flags = GETSTAT_ERROR;
+
+                return;
+            }
+
+            int f = PFIFO_POP;
+            int s = PFIFO_POP;
+            int m = PFIFO_POP;
+
+            if (!(VALID_BCD(m) && VALID_BCD(s) && VALID_BCD(f) && (f < 0x75))) {
+                cdrom->irq_delay = DELAY_1MS;
+                cdrom->delayed_command = CDL_ERROR;
+                cdrom->state = CD_STATE_ERROR;
+                cdrom->error = ERR_INVSUBF;
+                cdrom->error_flags = GETSTAT_ERROR;
+
+                return;
+            }
+
+            cdrom->seek_msf.m = m;
+            cdrom->seek_msf.s = s;
+            cdrom->seek_msf.f = f;
+
+            msf_from_bcd(&cdrom->seek_msf);
+
+            cdrom->cdda_msf = cdrom->seek_msf;
+
+            cdrom->seek_pending = 1;
+
+            log_fatal("setloc: %02x:%02x:%02x",
+                cdrom->seek_msf.m,
+                cdrom->seek_msf.s,
+                cdrom->seek_msf.f
+            );
+
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->delayed_command = CDL_SETLOC;
+            cdrom->state = CD_STATE_SEND_RESP1;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            SET_BITS(ifr, IFR_INT, IFR_INT3);
+            RESP_PUSH(GETSTAT_MOTOR);
+
+            if (cdrom->ongoing_read_command) {
+                // printf("command=%02x\n", cdrom->ongoing_read_command);
+                cdrom->state = CD_STATE_SEND_RESP2;
+                cdrom->delayed_command = cdrom->ongoing_read_command;
+                cdrom->irq_delay = DELAY_1MS;
+            } else {
+                cdrom->delayed_command = CDL_NONE;
+                cdrom->state = CD_STATE_RECV_CMD;
+            }
+        } break;
+
+        // Read ongoing
+        case CD_STATE_SEND_RESP2: {
+            int f = PFIFO_POP;
+            int s = PFIFO_POP;
+            int m = PFIFO_POP;
+
+            if (!(VALID_BCD(m) && VALID_BCD(s) && VALID_BCD(f) && (f < 0x75))) {
+                cdrom->ongoing_read_command = false;
+                cdrom->irq_delay = DELAY_1MS;
+                cdrom->delayed_command = CDL_ERROR;
+                cdrom->state = CD_STATE_ERROR;
+                cdrom->error = ERR_INVSUBF;
+                cdrom->error_flags = GETSTAT_ERROR;
+
+                return;
+            }
+
+            cdrom->seek_msf.m = m;
+            cdrom->seek_msf.s = s;
+            cdrom->seek_msf.f = f;
+        } break;
+    }
+}
+void cdrom_cmd_play(psx_cdrom_t* cdrom) {
+    cdrom->delayed_command = CDL_NONE;
+
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            int track = 0;
+
+            if (cdrom->cdda_playing) {
+                cdrom->pfifo_index = 0;
+
+                cdrom->irq_delay = DELAY_1MS;
+                cdrom->state = CD_STATE_SEND_RESP1;
+                cdrom->delayed_command = CDL_PLAY;
+
+                return;
+            }
+
+            // Optional track number parameter
+            if (cdrom->pfifo_index)
+                track = PFIFO_POP;
+
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->state = CD_STATE_SEND_RESP1;
+            cdrom->delayed_command = CDL_PLAY;
+
+            if (track) {
+                psx_disc_get_track_addr(cdrom->disc, &cdrom->cdda_msf, track);
+
+                msf_to_bcd(&cdrom->cdda_msf);
+
+                cdrom->cdda_track = track;
+                cdrom->seek_msf.m = cdrom->cdda_msf.m;
+                cdrom->seek_msf.s = cdrom->cdda_msf.s;
+                cdrom->seek_msf.f = cdrom->cdda_msf.f;
+    
+                cdrom->seek_pending = 1;
+            }
+
+            if (cdrom->seek_pending) {
+                cdrom->seek_pending = 0;
+
+                // printf("Seeked to location\n");
+
+                cdrom->cdda_msf = cdrom->seek_msf;
+
+                // Seek to that address and read sector
+                psx_disc_seek(cdrom->disc, cdrom->cdda_msf);
+                psx_disc_read_sector(cdrom->disc, cdrom->cdda_buf);
+
+                // Increment sector
+                msf_add_f(&cdrom->cdda_msf, 1);
+
+                cdrom->cdda_sector_offset = 0;
+            }
+
+            cdrom->cdda_playing = 1;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            SET_BITS(ifr, IFR_INT, IFR_INT3);
+            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_PLAY);
+
+            cdrom->delayed_command = CDL_NONE;
+            cdrom->state = CD_STATE_RECV_CMD;
+        } break;
+    }
+}
+void cdrom_cmd_readn(psx_cdrom_t* cdrom) {
+    cdrom->delayed_command = CDL_NONE;
+    cdrom->ongoing_read_command = CDL_READN;
+
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            log_fatal("CdlReadN: CD_STATE_RECV_CMD");
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->state = CD_STATE_SEND_RESP1;
+            cdrom->delayed_command = CDL_READN;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            log_fatal("CdlReadN: CD_STATE_SEND_RESP1");
+
+            SET_BITS(ifr, IFR_INT, IFR_INT3);
+            RESP_PUSH(GETSTAT_MOTOR);
+
+            if (cdrom->mode & MODE_XA_ADPCM) {
+                cdrom->xa_msf = cdrom->seek_msf;
+                cdrom->xa_playing = 1;
+
+                SET_BITS(status, STAT_ADPBUSY_MASK, STAT_ADPBUSY_MASK);
+
+                printf("Play XA-ADPCM encoded song at %02u:%02u:%02u, filter=%u, file=%02x, channel=%02x (ReadN)\n",
+                    cdrom->xa_msf.m,
+                    cdrom->xa_msf.s,
+                    cdrom->xa_msf.f,
+                    (cdrom->mode & MODE_XA_FILTER) != 0,
+                    cdrom->xa_file,
+                    cdrom->xa_channel
+                );
+            }
+
+            int err = psx_disc_seek(cdrom->disc, cdrom->seek_msf);
+
+            if (err) {
+                // log_set_quiet(0);
+                log_fatal("CdlReadN: Out of bounds seek");
+                log_set_quiet(1);
+
+                cdrom->irq_delay = DELAY_1MS * 600;
+                cdrom->delayed_command = CDL_ERROR;
+                cdrom->state = CD_STATE_ERROR;
+                cdrom->error = ERR_INVSUBF;
+                cdrom->error_flags = GETSTAT_SEEKERROR;
+
+                return;
+            }
+
+            psx_disc_read_sector(cdrom->disc, cdrom->dfifo);
+
+            int double_speed = cdrom->mode & MODE_SPEED;
+
+            cdrom->irq_delay = double_speed ? READ_DOUBLE_DELAY : READ_SINGLE_DELAY;
+            cdrom->state = CD_STATE_SEND_RESP2;
+            cdrom->delayed_command = CDL_READN;
+
+            if (cdrom->spin_delay) {
+                cdrom->irq_delay += cdrom->spin_delay;
+                cdrom->spin_delay = 0;
+            }
+        } break;
+
+        case CD_STATE_SEND_RESP2: {
+            log_fatal("CdlReadS: CD_STATE_SEND_RESP2");
+
+            // Returning only non-ADPCM sectors causes desync for some
+            // reason. I'll keep returning all sectors for now
+
+            if (cdrom->mode & MODE_XA_ADPCM) {
+                // printf("ReadS fetching non ADPCM sector...\n");
+                cdrom_fetch_video_sector(cdrom);
+
+                // printf("%02u:%02u:%02u - file=%02x channel=%02x sm=%02x ci=%02x\n",
+                //     cdrom->seek_msf.m,
+                //     cdrom->seek_msf.s,
+                //     cdrom->seek_msf.f,
+                //     cdrom->dfifo[0x10],
+                //     cdrom->dfifo[0x11],
+                //     cdrom->dfifo[0x12],
+                //     cdrom->dfifo[0x13]
+                // );
+            } else {
+                psx_disc_seek(cdrom->disc, cdrom->seek_msf);
+                psx_disc_read_sector(cdrom->disc, cdrom->dfifo);
+
+                msf_add_f(&cdrom->seek_msf, 1);
+            }
+
+            int double_speed = cdrom->mode & MODE_SPEED;
+
+            cdrom->irq_delay = double_speed ? READ_DOUBLE_DELAY : READ_SINGLE_DELAY;
+            cdrom->state = CD_STATE_SEND_RESP2;
+            cdrom->delayed_command = CDL_READN;
+            cdrom->dfifo_index = 0;
+
+            SET_BITS(ifr, IFR_INT, IFR_INT1);
+            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_READ);
+        } break;
+    }
+}
+void cdrom_cmd_motoron(psx_cdrom_t* cdrom) {
+    cdrom->delayed_command = CDL_NONE;
+
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            cdrom->ongoing_read_command = CDL_NONE;
+            cdrom->cdda_playing = 0;
+
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->state = CD_STATE_SEND_RESP1;
+            cdrom->delayed_command = CDL_MOTORON;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            SET_BITS(ifr, IFR_INT, 3);
+            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_READ);
+
+            int double_speed = cdrom->mode & MODE_SPEED;
+
+            cdrom->irq_delay = DELAY_1MS * (double_speed ? 70 : 65);
+            cdrom->state = CD_STATE_SEND_RESP2;
+            cdrom->delayed_command = CDL_MOTORON;
+        } break;
+
+        case CD_STATE_SEND_RESP2: {
+            SET_BITS(ifr, IFR_INT, 2);
+            RESP_PUSH(GETSTAT_MOTOR);
+
+            cdrom->state = CD_STATE_RECV_CMD;
+            cdrom->delayed_command = CDL_NONE;
+        } break;
+    }
+}
+void cdrom_cmd_stop(psx_cdrom_t* cdrom) {
+    cdrom->delayed_command = CDL_NONE;
+
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            cdrom->ongoing_read_command = CDL_NONE;
+            cdrom->cdda_playing = 0;
+
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->state = CD_STATE_SEND_RESP1;
+            cdrom->delayed_command = CDL_STOP;
+            cdrom->seek_msf.m = 0;
+            cdrom->seek_msf.s = 0;
+            cdrom->seek_msf.f = 0;
+
+            cdrom->cdda_msf = cdrom->seek_msf;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            SET_BITS(ifr, IFR_INT, 3);
+            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_READ);
+
+            int double_speed = cdrom->mode & MODE_SPEED;
+
+            cdrom->irq_delay = DELAY_1MS * (double_speed ? 70 : 65);
+            cdrom->state = CD_STATE_SEND_RESP2;
+            cdrom->delayed_command = CDL_STOP;
+        } break;
+
+        case CD_STATE_SEND_RESP2: {
+            SET_BITS(ifr, IFR_INT, 2);
+            RESP_PUSH(GETSTAT_MOTOR);
+
+            cdrom->state = CD_STATE_RECV_CMD;
+            cdrom->delayed_command = CDL_NONE;
+        } break;
+    }
+}
+void cdrom_cmd_pause(psx_cdrom_t* cdrom) {
+    cdrom->delayed_command = CDL_NONE;
+
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            cdrom->ongoing_read_command = CDL_NONE;
+            cdrom->cdda_playing = 0;
+            cdrom->xa_playing = 0;
+
+            SET_BITS(status, STAT_ADPBUSY_MASK, 0);
+
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->state = CD_STATE_SEND_RESP1;
+            cdrom->delayed_command = CDL_PAUSE;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            SET_BITS(ifr, IFR_INT, 3);
+            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_READ);
+
+            int double_speed = cdrom->mode & MODE_SPEED;
+
+            cdrom->irq_delay = DELAY_1MS * (double_speed ? 70 : 65);
+            cdrom->state = CD_STATE_SEND_RESP2;
+            cdrom->delayed_command = CDL_PAUSE;
+        } break;
+
+        case CD_STATE_SEND_RESP2: {
+            SET_BITS(ifr, IFR_INT, 2);
+            RESP_PUSH(GETSTAT_MOTOR);
+
+            cdrom->state = CD_STATE_RECV_CMD;
+            cdrom->delayed_command = CDL_NONE;
+        } break;
+    }
+}
+void cdrom_cmd_init(psx_cdrom_t* cdrom) {
+    cdrom->delayed_command = CDL_NONE;
+
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->state = CD_STATE_SEND_RESP1;
+            cdrom->delayed_command = CDL_INIT;
+            cdrom->ongoing_read_command = CDL_NONE;
+            cdrom->mode = 0;
+            cdrom->dfifo_index = 0;
+            cdrom->dfifo_full = 0;
+            cdrom->pfifo_index = 0;
+            cdrom->rfifo_index = 0;
+            cdrom->seek_msf.m = 0;
+            cdrom->seek_msf.s = 2;
+            cdrom->seek_msf.f = 0;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            SET_BITS(ifr, IFR_INT, 3);
+            RESP_PUSH(GETSTAT_MOTOR);
+
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->state = CD_STATE_SEND_RESP2;
+            cdrom->delayed_command = CDL_INIT;
+        } break;
+
+        case CD_STATE_SEND_RESP2: {
+            SET_BITS(ifr, IFR_INT, 2);
+            RESP_PUSH(GETSTAT_MOTOR);
+
+            cdrom->state = CD_STATE_RECV_CMD;
+            cdrom->delayed_command = CDL_NONE;
+        } break;
+    }
+}
+void cdrom_cmd_unmute(psx_cdrom_t* cdrom) {
+    cdrom->delayed_command = CDL_NONE;
+
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            if (cdrom->pfifo_index) {
+                log_fatal("CdlUnmute: Expected exactly 0 parameters");
+
+                cdrom->irq_delay = DELAY_1MS;
+                cdrom->delayed_command = CDL_ERROR;
+                cdrom->state = CD_STATE_ERROR;
+                cdrom->error = ERR_PCOUNT;
+                cdrom->error_flags = GETSTAT_ERROR;
+
+                return;
+            }
+
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->delayed_command = CDL_DEMUTE;
+            cdrom->state = CD_STATE_SEND_RESP1;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            SET_BITS(ifr, IFR_INT, IFR_INT3);
+            RESP_PUSH(cdrom->stat);
+
+            if (cdrom->ongoing_read_command) {
+                // printf("command=%02x\n", cdrom->ongoing_read_command);
+                cdrom->state = CD_STATE_SEND_RESP2;
+                cdrom->delayed_command = cdrom->ongoing_read_command;
+                cdrom->irq_delay = DELAY_1MS;
+            } else {
+                cdrom->delayed_command = CDL_NONE;
+                cdrom->state = CD_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+void cdrom_cmd_setfilter(psx_cdrom_t* cdrom) {
+    cdrom->delayed_command = CDL_NONE;
+
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            if (cdrom->pfifo_index != 2) {
+                log_fatal("CdlSetfilter: Expected exactly 2 parameter");
+
+                cdrom->irq_delay = DELAY_1MS;
+                cdrom->delayed_command = CDL_ERROR;
+                cdrom->state = CD_STATE_ERROR;
+                cdrom->error = ERR_PCOUNT;
+                cdrom->error_flags = GETSTAT_ERROR;
+
+                return;
+            }
+
+            cdrom->xa_channel = PFIFO_POP;
+            cdrom->xa_file = PFIFO_POP;
+
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->delayed_command = CDL_SETFILTER;
+            cdrom->state = CD_STATE_SEND_RESP1;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            cdrom->delayed_command = CDL_NONE;
+    
+            SET_BITS(ifr, IFR_INT, IFR_INT3);
+            RESP_PUSH(cdrom->stat);
+
+            if (cdrom->ongoing_read_command) {
+                cdrom->irq_delay = DELAY_1MS;
+                cdrom->delayed_command = cdrom->ongoing_read_command;
+                cdrom->state = CD_STATE_SEND_RESP2;
+            }
+
+            cdrom->state = CD_STATE_RECV_CMD;
+        } break;
+    }
+}
+void cdrom_cmd_setmode(psx_cdrom_t* cdrom) {
+    cdrom->delayed_command = CDL_NONE;
+
+    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);
+
+            if (cdrom->ongoing_read_command) {
+                cdrom->irq_delay = DELAY_1MS;
+                cdrom->delayed_command = cdrom->ongoing_read_command;
+                cdrom->state = CD_STATE_SEND_RESP2;
+            }
+
+            cdrom->state = CD_STATE_RECV_CMD;
+        } break;
+    }
+}
+void cdrom_cmd_getparam(psx_cdrom_t* cdrom) {
+    cdrom->delayed_command = CDL_NONE;
+
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->delayed_command = CDL_GETPARAM;
+            cdrom->state = CD_STATE_SEND_RESP1;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            cdrom->delayed_command = CDL_NONE;
+    
+            SET_BITS(ifr, IFR_INT, IFR_INT3);
+            RESP_PUSH(cdrom->xa_channel);
+            RESP_PUSH(cdrom->xa_file);
+            RESP_PUSH(0x00);
+            RESP_PUSH(cdrom->mode);
+            RESP_PUSH(GETSTAT_MOTOR);
+
+            cdrom->state = CD_STATE_RECV_CMD;
+        } break;
+    }
+}
+void cdrom_cmd_getlocl(psx_cdrom_t* cdrom) {
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->delayed_command = CDL_GETLOCL;
+            cdrom->state = CD_STATE_SEND_RESP1;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            SET_BITS(ifr, IFR_INT, IFR_INT3);
+            RESP_PUSH(cdrom->dfifo[0x13]);
+            RESP_PUSH(cdrom->dfifo[0x12]);
+            RESP_PUSH(cdrom->dfifo[0x11]);
+            RESP_PUSH(cdrom->dfifo[0x10]);
+            RESP_PUSH(cdrom->dfifo[0x0f]);
+            RESP_PUSH(cdrom->dfifo[0x0e]);
+            RESP_PUSH(cdrom->dfifo[0x0d]);
+            RESP_PUSH(cdrom->dfifo[0x0c]);
+
+            // if (cdrom->ongoing_read_command) {
+            //     // printf("command=%02x\n", cdrom->ongoing_read_command);
+            //     cdrom->state = CD_STATE_SEND_RESP2;
+            //     cdrom->delayed_command = cdrom->ongoing_read_command;
+            //     cdrom->irq_delay = DELAY_1MS;
+            // } else {
+                cdrom->delayed_command = CDL_NONE;
+                cdrom->state = CD_STATE_RECV_CMD;
+            // }
+        } break;
+    }
+}
+void cdrom_cmd_getlocp(psx_cdrom_t* cdrom) {
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->delayed_command = CDL_GETLOCP;
+            cdrom->state = CD_STATE_SEND_RESP1;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            msf_t absolute = cdrom->xa_playing ? cdrom->xa_msf : cdrom->seek_msf;
+            msf_t relative = absolute;
+            msf_t track_msf = cdrom_get_track_addr(cdrom, absolute);
+
+            relative.m -= track_msf.m;
+            relative.s -= track_msf.s;
+            relative.f -= track_msf.f;
+
+            msf_adjust_sub(&relative);
+
+            // printf("abs=%02u:%02u:%02u tra=%02u:%02u:%02u rel=%02u:%02u:%02u\n",
+            //     absolute.m,
+            //     absolute.s,
+            //     absolute.f,
+            //     track_msf.m,
+            //     track_msf.s,
+            //     track_msf.f,
+            //     relative.m,
+            //     relative.s,
+            //     relative.f
+            // );
+
+            msf_to_bcd(&absolute);
+            msf_to_bcd(&relative);
+
+            // printf("getlocp 01 01 %02x:%02x:%02x %02x:%02x:%02x\n",
+            //     relative.m,
+            //     relative.s,
+            //     relative.f,
+            //     absolute.m,
+            //     absolute.s,
+            //     absolute.f
+            // );
+
+            SET_BITS(ifr, IFR_INT, IFR_INT3);
+            RESP_PUSH(absolute.f);
+            RESP_PUSH(absolute.s);
+            RESP_PUSH(absolute.m);
+            RESP_PUSH(relative.f);
+            RESP_PUSH(relative.s);
+            RESP_PUSH(relative.m);
+            RESP_PUSH(0x01);
+            RESP_PUSH(0x01);
+
+            if (cdrom->ongoing_read_command) {
+                //// printf("command=%02x\n", cdrom->ongoing_read_command);
+                cdrom->state = CD_STATE_SEND_RESP2;
+                cdrom->delayed_command = cdrom->ongoing_read_command;
+                cdrom->irq_delay = DELAY_1MS;
+            } else {
+                cdrom->delayed_command = CDL_NONE;
+                cdrom->state = CD_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+void cdrom_cmd_setsession(psx_cdrom_t* cdrom) {
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->delayed_command = CDL_SETSESSION;
+            cdrom->state = CD_STATE_SEND_RESP1;
+
+            cdrom->pfifo_index = 0;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            SET_BITS(ifr, IFR_INT, IFR_INT3);
+            RESP_PUSH(GETSTAT_SEEK | GETSTAT_MOTOR);
+
+            cdrom->delayed_command = CDL_SETSESSION;
+            cdrom->state = CD_STATE_SEND_RESP2;
+        } break;
+
+        case CD_STATE_SEND_RESP2: {
+            SET_BITS(ifr, IFR_INT, IFR_INT2);
+            RESP_PUSH(GETSTAT_SEEK | GETSTAT_MOTOR);
+
+            cdrom->delayed_command = CDL_NONE;
+            cdrom->state = CD_STATE_RECV_CMD;
+        } break;
+    }
+}
+void cdrom_cmd_gettn(psx_cdrom_t* cdrom) {
+    cdrom->delayed_command = CDL_NONE;
+
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            if (cdrom->pfifo_index) {
+                log_fatal("CdlGetTN: Expected exactly 0 parameters");
+
+                cdrom->irq_delay = DELAY_1MS;
+                cdrom->delayed_command = CDL_ERROR;
+                cdrom->state = CD_STATE_ERROR;
+                cdrom->error = ERR_PCOUNT;
+                cdrom->error_flags = GETSTAT_ERROR;
+
+                return;
+            }
+
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->delayed_command = CDL_GETTN;
+            cdrom->state = CD_STATE_SEND_RESP1;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            int tn;
+
+            psx_disc_get_track_count(cdrom->disc, &tn);
+
+            SET_BITS(ifr, IFR_INT, IFR_INT3);
+            RESP_PUSH(tn);
+            RESP_PUSH(0x01);
+            RESP_PUSH(GETSTAT_MOTOR);
+
+            cdrom->delayed_command = CDL_NONE;
+            cdrom->state = CD_STATE_RECV_CMD;
+        } break;
+    }
+}
+void cdrom_cmd_gettd(psx_cdrom_t* cdrom) {
+    cdrom->delayed_command = CDL_NONE;
+
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            if (cdrom->pfifo_index != 1) {
+                log_fatal("CdlGetTD: Expected exactly 1 parameter");
+
+                cdrom->irq_delay = DELAY_1MS;
+                cdrom->delayed_command = CDL_ERROR;
+                cdrom->state = CD_STATE_ERROR;
+                cdrom->error = ERR_PCOUNT;
+                cdrom->error_flags = GETSTAT_ERROR;
+
+                return;
+            }
+
+            cdrom->gettd_track = PFIFO_POP;
+
+            if (!VALID_BCD(cdrom->gettd_track)) {
+                cdrom->irq_delay = DELAY_1MS;
+                cdrom->delayed_command = CDL_ERROR;
+                cdrom->state = CD_STATE_ERROR;
+                cdrom->error = ERR_INVSUBF;
+                cdrom->error_flags = GETSTAT_ERROR;
+
+                return;
+            }
+
+            int err = psx_disc_get_track_addr(cdrom->disc, NULL, cdrom->gettd_track);
+
+            if (err) {
+                cdrom->irq_delay = DELAY_1MS;
+                cdrom->delayed_command = CDL_ERROR;
+                cdrom->state = CD_STATE_ERROR;
+                cdrom->error = ERR_INVSUBF;
+                cdrom->error_flags = GETSTAT_ERROR;
+
+                return;
+            }
+
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->delayed_command = CDL_GETTD;
+            cdrom->state = CD_STATE_SEND_RESP1;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            msf_t td;
+
+            psx_disc_get_track_addr(cdrom->disc, &td, cdrom->gettd_track);
+
+            SET_BITS(ifr, IFR_INT, IFR_INT3);
+            RESP_PUSH(ITOB(td.s));
+            RESP_PUSH(ITOB(td.m));
+            RESP_PUSH(GETSTAT_MOTOR);
+
+            cdrom->delayed_command = CDL_NONE;
+            cdrom->state = CD_STATE_RECV_CMD;
+        } break;
+    }
+}
+void cdrom_cmd_seekl(psx_cdrom_t* cdrom) {
+    cdrom->delayed_command = CDL_NONE;
+
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            if (cdrom->pfifo_index) {
+                log_fatal("CdlSeekL: Expected exactly 0 parameters");
+
+                cdrom->irq_delay = DELAY_1MS;
+                cdrom->delayed_command = CDL_ERROR;
+                cdrom->state = CD_STATE_ERROR;
+                cdrom->error = ERR_PCOUNT;
+                cdrom->error_flags = GETSTAT_ERROR;
+
+                return;
+            }
+
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->state = CD_STATE_SEND_RESP1;
+            cdrom->delayed_command = CDL_SEEKL;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            SET_BITS(ifr, IFR_INT, 3);
+            RESP_PUSH(GETSTAT_MOTOR);
+
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->state = CD_STATE_SEND_RESP2;
+            cdrom->delayed_command = CDL_SEEKL;
+
+            psx_disc_seek(cdrom->disc, cdrom->seek_msf);
+
+            cdrom->seek_pending = 0;
+        } break;
+
+        case CD_STATE_SEND_RESP2: {
+            SET_BITS(ifr, IFR_INT, 2);
+            RESP_PUSH(GETSTAT_MOTOR);
+
+            cdrom->state = CD_STATE_RECV_CMD;
+            cdrom->delayed_command = CDL_NONE;
+        } break;
+    }
+}
+void cdrom_cmd_seekp(psx_cdrom_t* cdrom) {
+    cdrom->delayed_command = CDL_NONE;
+
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            if (cdrom->pfifo_index) {
+                log_fatal("CdlSeekP: Expected exactly 0 parameters");
+
+                cdrom->irq_delay = DELAY_1MS;
+                cdrom->delayed_command = CDL_ERROR;
+                cdrom->state = CD_STATE_ERROR;
+                cdrom->error = ERR_PCOUNT;
+                cdrom->error_flags = GETSTAT_ERROR;
+
+                return;
+            }
+
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->state = CD_STATE_SEND_RESP1;
+            cdrom->delayed_command = CDL_SEEKP;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            SET_BITS(ifr, IFR_INT, 3);
+            RESP_PUSH(GETSTAT_MOTOR);
+
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->state = CD_STATE_SEND_RESP2;
+            cdrom->delayed_command = CDL_SEEKP;
+
+            psx_disc_seek(cdrom->disc, cdrom->seek_msf);
+
+            cdrom->seek_pending = 0;
+        } break;
+
+        case CD_STATE_SEND_RESP2: {
+            SET_BITS(ifr, IFR_INT, 2);
+            RESP_PUSH(GETSTAT_MOTOR);
+
+            cdrom->state = CD_STATE_RECV_CMD;
+            cdrom->delayed_command = CDL_NONE;
+        } break;
+    }
+}
+void cdrom_cmd_test(psx_cdrom_t* cdrom) {
+    cdrom->delayed_command = CDL_NONE;
+
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            if (cdrom->pfifo_index != 1) {
+                log_fatal("CdlTest: Expected exactly 1 parameter");
+
+                cdrom->irq_delay = DELAY_1MS;
+                cdrom->delayed_command = CDL_ERROR;
+                cdrom->state = CD_STATE_ERROR;
+                cdrom->error = ERR_PCOUNT;
+                cdrom->error_flags = GETSTAT_ERROR;
+
+                return;
+            }
+
+            if (PFIFO_POP != 0x20) {
+                log_fatal("CdlTest: Unhandled subcommand");
+
+                cdrom->irq_delay = DELAY_1MS;
+                cdrom->delayed_command = CDL_ERROR;
+                cdrom->state = CD_STATE_ERROR;
+                cdrom->error = ERR_INVSUBF;
+
+                return;
+            }
+
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->delayed_command = CDL_TEST;
+            cdrom->state = CD_STATE_SEND_RESP1;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            cdrom->delayed_command = CDL_NONE;
+
+            // 95h,05h,16h,C1h
+            SET_BITS(ifr, IFR_INT, IFR_INT3);
+            RESP_PUSH(0xc1);
+            RESP_PUSH(0x16);
+            RESP_PUSH(0x05);
+            RESP_PUSH(0x95);
+
+            cdrom->state = CD_STATE_RECV_CMD;
+        } break;
+    }
+}
+void cdrom_cmd_getid(psx_cdrom_t* cdrom) {
+    cdrom->delayed_command = CDL_NONE;
+
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            if (cdrom->pfifo_index) {
+                log_fatal("CdlGetID: Expected exactly 0 parameters");
+
+                cdrom->irq_delay = DELAY_1MS;
+                cdrom->delayed_command = CDL_ERROR;
+                cdrom->state = CD_STATE_ERROR;
+                cdrom->error = ERR_PCOUNT;
+                cdrom->error_flags = GETSTAT_ERROR;
+
+                return;
+            }
+
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->state = CD_STATE_SEND_RESP1;
+            cdrom->delayed_command = CDL_GETID;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            SET_BITS(ifr, IFR_INT, 3);
+            RESP_PUSH(GETSTAT_MOTOR);
+
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->state = CD_STATE_SEND_RESP2;
+            cdrom->delayed_command = CDL_GETID;
+        } break;
+
+        case CD_STATE_SEND_RESP2: {
+            if (cdrom->disc) {
+                SET_BITS(ifr, IFR_INT, 2);
+
+                switch (cdrom->cd_type) {
+                    case CDT_LICENSED: {
+                        for (int i = 0; i < GETID_RESPONSE_SIZE; i++)
+                            RESP_PUSH(g_getid_licensed[GETID_RESPONSE_END - i]);
+                    } break;
+
+                    case CDT_AUDIO: {
+                        for (int i = 0; i < GETID_RESPONSE_SIZE; i++)
+                            RESP_PUSH(g_getid_audio[GETID_RESPONSE_END - i]);
+                    } break;
+
+                    case CDT_UNKNOWN: {
+                        for (int i = 0; i < GETID_RESPONSE_SIZE; i++)
+                            RESP_PUSH(g_getid_unlicensed[GETID_RESPONSE_END - i]);
+                    } break;
+                }
+            } else {
+                SET_BITS(ifr, IFR_INT, 5);
+
+                for (int i = 0; i < GETID_RESPONSE_SIZE; i++)
+                    RESP_PUSH(g_getid_no_disc[GETID_RESPONSE_END - i]);
+            }
+
+            cdrom->state = CD_STATE_RECV_CMD;
+            cdrom->delayed_command = CDL_NONE;
+        } break;
+    }
+}
+void cdrom_cmd_reads(psx_cdrom_t* cdrom) {
+    cdrom->delayed_command = CDL_NONE;
+    cdrom->ongoing_read_command = CDL_READS;
+
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            log_fatal("CdlReadS: CD_STATE_RECV_CMD");
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->state = CD_STATE_SEND_RESP1;
+            cdrom->delayed_command = CDL_READS;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            printf("CdlReadS: CD_STATE_SEND_RESP1\n");
+            log_fatal("CdlReadS: CD_STATE_SEND_RESP1");
+
+            SET_BITS(ifr, IFR_INT, IFR_INT3);
+            RESP_PUSH(GETSTAT_MOTOR);
+
+            if (cdrom->mode & MODE_XA_ADPCM) {
+                cdrom->xa_msf = cdrom->seek_msf;
+                cdrom->xa_playing = 1;
+
+                SET_BITS(status, STAT_ADPBUSY_MASK, STAT_ADPBUSY_MASK);
+
+                printf("Play XA-ADPCM encoded song at %02u:%02u:%02u, filter=%u, file=%02x, channel=%02x (ReadS)\n",
+                    cdrom->xa_msf.m,
+                    cdrom->xa_msf.s,
+                    cdrom->xa_msf.f,
+                    (cdrom->mode & MODE_XA_FILTER) != 0,
+                    cdrom->xa_file,
+                    cdrom->xa_channel
+                );
+
+                int double_speed = cdrom->mode & MODE_SPEED;
+
+                cdrom->irq_delay = double_speed ? READ_DOUBLE_DELAY : READ_SINGLE_DELAY;
+                cdrom->state = CD_STATE_SEND_RESP2;
+                cdrom->delayed_command = CDL_READS;
+
+                // if (cdrom->spin_delay) {
+                //     cdrom->irq_delay += cdrom->spin_delay;
+                //     cdrom->spin_delay = 0;
+                // }
+
+                return;
+            }
+
+            int err = psx_disc_seek(cdrom->disc, cdrom->seek_msf);
+
+            if (err) {
+                log_fatal("CdlReadS: Out of bounds seek");
+
+                cdrom->irq_delay = DELAY_1MS * 600;
+                cdrom->delayed_command = CDL_ERROR;
+                cdrom->state = CD_STATE_ERROR;
+                cdrom->error = ERR_INVSUBF;
+                cdrom->error_flags = GETSTAT_SEEKERROR;
+
+                return;
+            }
+
+            int double_speed = cdrom->mode & MODE_SPEED;
+
+            cdrom->irq_delay = double_speed ? READ_DOUBLE_DELAY : READ_SINGLE_DELAY;
+            cdrom->state = CD_STATE_SEND_RESP2;
+            cdrom->delayed_command = CDL_READS;
+
+            if (cdrom->spin_delay) {
+                cdrom->irq_delay += cdrom->spin_delay;
+                cdrom->spin_delay = 0;
+            }
+        } break;
+
+        case CD_STATE_SEND_RESP2: {
+            log_fatal("CdlReadS: CD_STATE_SEND_RESP2");
+
+            // Returning only non-ADPCM sectors causes desync for some
+            // reason. I'll keep returning all sectors for now
+
+            if (cdrom->mode & MODE_XA_ADPCM) {
+                // printf("ReadS fetching non ADPCM sector...\n");
+                cdrom_fetch_video_sector(cdrom);
+
+                // printf("%02u:%02u:%02u - file=%02x channel=%02x sm=%02x ci=%02x\n",
+                //     cdrom->seek_msf.m,
+                //     cdrom->seek_msf.s,
+                //     cdrom->seek_msf.f,
+                //     cdrom->dfifo[0x10],
+                //     cdrom->dfifo[0x11],
+                //     cdrom->dfifo[0x12],
+                //     cdrom->dfifo[0x13]
+                // );
+            } else {
+                psx_disc_seek(cdrom->disc, cdrom->seek_msf);
+                psx_disc_read_sector(cdrom->disc, cdrom->dfifo);
+
+                msf_add_f(&cdrom->seek_msf, 1);
+            }
+
+            int double_speed = cdrom->mode & MODE_SPEED;
+
+            cdrom->irq_delay = double_speed ? READ_DOUBLE_DELAY : READ_SINGLE_DELAY;
+            cdrom->state = CD_STATE_SEND_RESP2;
+            cdrom->delayed_command = CDL_READS;
+            cdrom->dfifo_index = 0;
+
+            SET_BITS(ifr, IFR_INT, IFR_INT1);
+            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_READ);
+        } break;
+    }
+}
+void cdrom_cmd_readtoc(psx_cdrom_t* cdrom) {
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            cdrom->status |= STAT_BUSYSTS_MASK;
+            cdrom->irq_delay = DELAY_1MS * 1000;
+            cdrom->state = CD_STATE_SEND_RESP1;
+            cdrom->delayed_command = CDL_READTOC;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            cdrom->status &= ~STAT_BUSYSTS_MASK;
+            SET_BITS(ifr, IFR_INT, 3);
+            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_READ);
+
+            cdrom->irq_delay = DELAY_1MS * 1000;
+            cdrom->state = CD_STATE_SEND_RESP2;
+            cdrom->delayed_command = CDL_READTOC;
+        } break;
+
+        case CD_STATE_SEND_RESP2: {
+            SET_BITS(ifr, IFR_INT, 2);
+            RESP_PUSH(GETSTAT_MOTOR);
+
+            cdrom->state = CD_STATE_RECV_CMD;
+            cdrom->delayed_command = CDL_NONE;
+        } break;
+    }
+}
+void cdrom_cmd_videocd(psx_cdrom_t* cdrom) {
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->state = CD_STATE_SEND_RESP1;
+            cdrom->delayed_command = CDL_VIDEOCD;
+            cdrom->pfifo_index = 0;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            printf("VideoCD task %02x\n", cdrom->pfifo[4]);
+            SET_BITS(ifr, IFR_INT, 3);
+
+            switch (cdrom->pfifo[4]) {
+                case 0: {
+                    RESP_PUSH(0x00);
+                    RESP_PUSH(0x00);
+                    RESP_PUSH(0x00);
+                    RESP_PUSH(0x00);
+                    RESP_PUSH(0x00);
+                    RESP_PUSH(GETSTAT_MOTOR);
+                } break;
+
+                case 1: {
+                    RESP_PUSH(0x00);
+                    RESP_PUSH(0x00);
+                    RESP_PUSH(0x00);
+                    RESP_PUSH(0x00);
+                    RESP_PUSH(0x81);
+                    RESP_PUSH(GETSTAT_MOTOR);
+                } break;
+
+                case 2: {
+                    RESP_PUSH(0x00);
+                    RESP_PUSH(0x00);
+                    RESP_PUSH(0x00);
+                    RESP_PUSH(0x00);
+                    RESP_PUSH(0x05);
+                    RESP_PUSH(GETSTAT_MOTOR);
+                } break;
+            }
+
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->state = CD_STATE_RECV_CMD;
+            cdrom->delayed_command = CDL_NONE;
+        } break;
+    }
+}
+
+typedef void (*cdrom_cmd_t)(psx_cdrom_t*);
+
+const char* g_psx_cdrom_command_names[] = {
+    "CdlUnimplemented",
+    "CdlGetstat",
+    "CdlSetloc",
+    "CdlPlay",
+    "CdlUnimplemented",
+    "CdlUnimplemented",
+    "CdlReadn",
+    "CdlMotoron",
+    "CdlStop",
+    "CdlPause",
+    "CdlInit",
+    "CdlUnimplemented",
+    "CdlUnmute",
+    "CdlSetfilter",
+    "CdlSetmode",
+    "CdlGetparam",
+    "CdlGetlocl",
+    "CdlGetlocp",
+    "CdlSetsession",
+    "CdlGettn",
+    "CdlGettd",
+    "CdlSeekl",
+    "CdlSeekp",
+    "CdlUnimplemented",
+    "CdlUnimplemented",
+    "CdlTest",
+    "CdlGetid",
+    "CdlReads",
+    "CdlUnimplemented",
+    "CdlUnimplemented",
+    "CdlReadtoc",
+    "CdlVideoCD"
+};
+
+cdrom_cmd_t g_psx_cdrom_command_table[] = {
+    cdrom_cmd_unimplemented,
+    cdrom_cmd_getstat,
+    cdrom_cmd_setloc,
+    cdrom_cmd_play,
+    cdrom_cmd_unimplemented,
+    cdrom_cmd_unimplemented,
+    cdrom_cmd_readn,
+    cdrom_cmd_motoron,
+    cdrom_cmd_stop,
+    cdrom_cmd_pause,
+    cdrom_cmd_init,
+    cdrom_cmd_unmute,
+    cdrom_cmd_unmute,
+    cdrom_cmd_setfilter,
+    cdrom_cmd_setmode,
+    cdrom_cmd_getparam,
+    cdrom_cmd_getlocl,
+    cdrom_cmd_getlocp,
+    cdrom_cmd_setsession,
+    cdrom_cmd_gettn,
+    cdrom_cmd_gettd,
+    cdrom_cmd_seekl,
+    cdrom_cmd_seekp,
+    cdrom_cmd_unimplemented,
+    cdrom_cmd_unimplemented,
+    cdrom_cmd_test,
+    cdrom_cmd_getid,
+    cdrom_cmd_reads,
+    cdrom_cmd_unimplemented,
+    cdrom_cmd_unimplemented,
+    cdrom_cmd_readtoc,
+    cdrom_cmd_videocd,
+
+    // Actually an unimplemented command, we use this
+    // index for CD error handling
+    cdrom_cmd_error
+};
+
+typedef uint8_t (*psx_cdrom_read_function_t)(psx_cdrom_t*);
+typedef void (*psx_cdrom_write_function_t)(psx_cdrom_t*, uint8_t);
+
+uint8_t cdrom_read_status(psx_cdrom_t* cdrom) {
+    return cdrom->status;
+}
+
+uint8_t cdrom_read_rfifo(psx_cdrom_t* cdrom) {
+    uint8_t data = cdrom->rfifo[--cdrom->rfifo_index];
+
+    if (cdrom->rfifo_index == 0)
+        SET_BITS(status, STAT_RSLRRDY_MASK, 0);
+    
+    return data;
+}
+
+uint8_t cdrom_read_dfifo(psx_cdrom_t* cdrom) {
+    if (!cdrom->dfifo_full)
+        return 0;
+
+    int sector_size_bit = cdrom->mode & MODE_SECTOR_SIZE;
+
+    uint32_t sector_size = sector_size_bit ? 0x924 : 0x800;
+    uint32_t offset = sector_size_bit ? 12 : 24;
+
+    if (cdrom->dfifo_index < sector_size) {
+        SET_BITS(status, STAT_DRQSTS_MASK, STAT_DRQSTS_MASK);
+
+        uint8_t data = cdrom->dfifo[offset + (cdrom->dfifo_index++)];
+
+        if (cdrom->dfifo_index >= sector_size)
+            SET_BITS(status, STAT_DRQSTS_MASK, 0);
+
+        return data;
+    }
+
+    return 0;
+}
+
+uint8_t cdrom_read_ier(psx_cdrom_t* cdrom) {
+    return cdrom->ier;
+}
+
+uint8_t cdrom_read_ifr(psx_cdrom_t* cdrom) {
+    return 0xe0 | cdrom->ifr;
+}
+
+void cdrom_write_status(psx_cdrom_t* cdrom, uint8_t value) {
+    SET_BITS(status, STAT_INDEX_MASK, value);
+}
+
+void cdrom_write_cmd(psx_cdrom_t* cdrom, uint8_t value) {
+    printf("%s(%02x) %u params=[%02x, %02x, %02x, %02x, %02x, %02x]\n",
+        g_psx_cdrom_command_names[value],
+        value,
+        cdrom->pfifo_index,
+        cdrom->pfifo[0],
+        cdrom->pfifo[1],
+        cdrom->pfifo[2],
+        cdrom->pfifo[3],
+        cdrom->pfifo[4],
+        cdrom->pfifo[5]
+    );
+
+    cdrom->command = value;
+    cdrom->state = CD_STATE_RECV_CMD;
+
+    g_psx_cdrom_command_table[value](cdrom);
+}
+
+void cdrom_write_pfifo(psx_cdrom_t* cdrom, uint8_t value) {
+    cdrom->pfifo[(cdrom->pfifo_index++) & 0xf] = value;
+
+    SET_BITS(status, STAT_PRMWRDY_MASK, (cdrom->pfifo_index & 0x10) ? 0x0 : 0xff);
+
+    cdrom->pfifo_index &= 0x1f;
+}
+
+void cdrom_write_req(psx_cdrom_t* cdrom, uint8_t value) {
+    if (value & REQ_BFRD) {
+        SET_BITS(status, STAT_DRQSTS_MASK, STAT_DRQSTS_MASK);
+
+        cdrom->dfifo_full = 1;
+    } else {
+        SET_BITS(status, STAT_DRQSTS_MASK, 0);
+
+        cdrom->dfifo_full = 0;
+    }
+}
+
+void cdrom_write_smdout(psx_cdrom_t* cdrom, uint8_t value) {
+    log_fatal("Sound map data out unimplemented");
+}
+
+void cdrom_write_ier(psx_cdrom_t* cdrom, uint8_t value) {
+    cdrom->ier = value;
+}
+
+void cdrom_write_ifr(psx_cdrom_t* cdrom, uint8_t value) {
+    cdrom->ifr &= ~(value & 0x1f);
+
+    // Clear Parameter FIFO
+    if (value & 0x40) {
+        cdrom->pfifo_index = 0;
+
+        SET_BITS(
+            status,
+            (STAT_PRMEMPT_MASK | STAT_PRMWRDY_MASK),
+            (STAT_PRMEMPT_MASK | STAT_PRMWRDY_MASK)
+        );
+    }
+}
+
+void cdrom_write_sminfo(psx_cdrom_t* cdrom, uint8_t value) {
+    log_fatal("Sound map coding info unimplemented");
+}
+
+void cdrom_write_lcdlspuv(psx_cdrom_t* cdrom, uint8_t value) {
+    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));
+}
+
+#define XA_STEREO_SAMPLES 2016
+#define XA_MONO_SAMPLES 4032
+#define XA_DECODED_SAMPLES 37632
+#define XA_RINGBUF_SIZE 32
+#define XA_STEREO_RESAMPLE_SIZE 2352
+#define XA_MONO_RESAMPLE_SIZE 4704
+
+void psx_cdrom_init(psx_cdrom_t* cdrom, psx_ic_t* ic) {
+    memset(cdrom, 0, sizeof(psx_cdrom_t));
+
+    cdrom->io_base = PSX_CDROM_BEGIN;
+    cdrom->io_size = PSX_CDROM_SIZE;
+
+    cdrom->ic = ic;
+    cdrom->status = STAT_PRMEMPT_MASK | STAT_PRMWRDY_MASK | STAT_RSLRRDY_MASK;
+    cdrom->dfifo = malloc(CD_SECTOR_SIZE);
+    cdrom->cdda_buf = malloc(CD_SECTOR_SIZE);
+
+    // Initialize XA state
+    cdrom->xa_sector_buf = malloc(CD_SECTOR_SIZE);
+    cdrom->xa_left_buf = malloc(XA_STEREO_SAMPLES * sizeof(int16_t));
+    cdrom->xa_right_buf = malloc(XA_STEREO_SAMPLES * sizeof(int16_t));
+    cdrom->xa_mono_buf = malloc(XA_MONO_SAMPLES * sizeof(int16_t));
+    cdrom->xa_upsample_buf = malloc(((14112 * 2) + 6) * sizeof(int16_t));
+    cdrom->xa_left_resample_buf = malloc((XA_STEREO_RESAMPLE_SIZE * 2) * sizeof(int16_t));
+    cdrom->xa_right_resample_buf = malloc((XA_STEREO_RESAMPLE_SIZE * 2) * sizeof(int16_t));
+    cdrom->xa_mono_resample_buf = malloc((XA_MONO_RESAMPLE_SIZE * 2) * sizeof(int16_t));
+    cdrom->xa_step = 6;
+
+    // We will use this whenever we implement proper
+    // XA interpolation
+    (void)g_zigzag_table;
+
+    memset(cdrom->xa_left_buf, 0, XA_STEREO_SAMPLES * sizeof(int16_t));
+    memset(cdrom->xa_right_buf, 0, XA_STEREO_SAMPLES * sizeof(int16_t));
+    memset(cdrom->xa_mono_buf, 0, XA_MONO_SAMPLES * sizeof(int16_t));
+    memset(cdrom->xa_upsample_buf, 0, ((14112 * 2) + 6) * sizeof(int16_t));
+    memset(cdrom->xa_left_resample_buf, 0, (XA_STEREO_RESAMPLE_SIZE * 2) * sizeof(int16_t));
+    memset(cdrom->xa_right_resample_buf, 0, (XA_STEREO_RESAMPLE_SIZE * 2) * sizeof(int16_t));
+    memset(cdrom->xa_mono_resample_buf, 0, (XA_MONO_RESAMPLE_SIZE * 2) * sizeof(int16_t));
+
+    cdrom->seek_msf.m = 0;
+    cdrom->seek_msf.s = 2;
+    cdrom->seek_msf.f = 0;
+}
+
+uint32_t psx_cdrom_read32(psx_cdrom_t* cdrom, uint32_t offset) {
+    // log_set_quiet(0);
+    log_fatal("Unhandled 32-bit CDROM read at offset %08x", offset);
+
+    // exit(1);
+
+    return 0x0;
+}
+
+uint16_t psx_cdrom_read16(psx_cdrom_t* cdrom, uint32_t offset) {
+    // log_set_quiet(0);
+    log_fatal("Unhandled 16-bit CDROM read at offset %08x", offset);
+
+    // exit(1);
+
+    return 0x0;
+}
+
+uint8_t psx_cdrom_read8(psx_cdrom_t* cdrom, uint32_t offset) {
+    uint8_t data = g_psx_cdrom_read_table[(STAT_INDEX << 2) | offset](cdrom);
+
+    if (((STAT_INDEX << 2) | offset) == 2)
+        return data;
+
+    // // log_set_quiet(0);
+    // log_fatal("%s (read %02x)", g_psx_cdrom_read_names_table[(STAT_INDEX << 2) | offset], data);
+    // log_set_quiet(1);
+
+    return data;
+}
+
+void psx_cdrom_write32(psx_cdrom_t* cdrom, uint32_t offset, uint32_t value) {
+    // log_set_quiet(0);
+    log_fatal("Unhandled 32-bit CDROM write at offset %08x (%08x)", offset, value);
+
+    // exit(1);
+}
+
+void psx_cdrom_write16(psx_cdrom_t* cdrom, uint32_t offset, uint16_t value) {
+    // log_set_quiet(0);
+    log_fatal("Unhandled 16-bit CDROM write at offset %08x (%04x)", offset, value);
+
+    // exit(1);
+}
+
+void psx_cdrom_write8(psx_cdrom_t* cdrom, uint32_t offset, uint8_t value) {
+    // // log_set_quiet(0);
+    // log_fatal("%s (write %02x)", g_psx_cdrom_write_names_table[(STAT_INDEX << 2) | offset], value);
+    // log_set_quiet(1);
+
+    g_psx_cdrom_write_table[(STAT_INDEX << 2) | offset](cdrom, value);
+}
+
+void psx_cdrom_update(psx_cdrom_t* cdrom, int cyc) {
+    if (cdrom->irq_delay) {
+        cdrom->irq_delay -= cyc;
+
+        if (cdrom->irq_delay <= 0) {
+            if (!cdrom->irq_disable) {
+                psx_ic_irq(cdrom->ic, IC_CDROM);
+            } else {
+                cdrom->irq_disable = 0;
+            }
+
+            cdrom->irq_delay = 0;
+
+            if (cdrom->delayed_command) {
+                // // log_set_quiet(0);
+                // log_fatal("%s(%02x) (Delayed)",
+                //     g_psx_cdrom_command_names[cdrom->delayed_command],
+                //     cdrom->delayed_command
+                // );
+                // log_set_quiet(1);
+                g_psx_cdrom_command_table[cdrom->delayed_command](cdrom);
+            }
+
+            // // log_set_quiet(0);
+            // log_fatal("CDROM INT%u", cdrom->ifr & 0x7);
+            // log_set_quiet(1);
+        }
+    }
+}
+
+const char* g_psx_cdrom_extensions[] = {
+    "cue",
+    "bin",
+    0
+};
+
+enum {
+    CD_EXT_CUE,
+    CD_EXT_BIN,
+    CD_EXT_UNSUPPORTED
+};
+
+int cdrom_get_extension(const char* path) {
+    const char* ptr = &path[strlen(path) - 1];
+    int i = 0;
+
+    while ((*ptr != '.') && (ptr != path))
+        ptr--;
+    
+    if (ptr == path)
+        return CD_EXT_UNSUPPORTED;
+
+    while (g_psx_cdrom_extensions[i]) {
+        if (!strcmp(ptr + 1, g_psx_cdrom_extensions[i]))
+            return i;
+        
+        ++i;
+    }
+
+    return CD_EXT_UNSUPPORTED;
+}
+
+void cdrom_check_cd_type(psx_cdrom_t* cdrom) {
+    char buf[CD_SECTOR_SIZE];
+
+    // Seek to Primary Volume Descriptor
+    msf_t pvd = { 0, 2, 16 };
+
+    // If the disc is smaller than 16 sectors
+    // then it can't be a PlayStation game.
+    // Audio discs should also have ISO volume
+    // descriptors, so it's probably something else
+    // entirely.
+    if (psx_disc_seek(cdrom->disc, pvd)) {
+        cdrom->cd_type = CDT_UNKNOWN;
+
+        return;
+    }
+
+    psx_disc_read_sector(cdrom->disc, buf);
+
+    // Check for the "PLAYSTATION" string at PVD offset 20h
+
+    // Patch 20 byte so comparison is done correctly
+    buf[0x2b] = 0;
+
+    if (strncmp(&buf[0x20], "PLAYSTATION", 12)) {
+        cdrom->cd_type = CDT_AUDIO;
+
+        return;
+    }
+
+    cdrom->cd_type = CDT_LICENSED;
+}
+
+void psx_cdrom_open(psx_cdrom_t* cdrom, const char* path) {
+    cdrom->disc = psx_disc_create();
+
+    int len = strlen(path);
+
+    char* lower = malloc(len + 1);
+
+    for (int i = 0; i < len; i++)
+        lower[i] = tolower(path[i]);
+
+    lower[len] = '\0';
+
+    int ext = cdrom_get_extension(lower);
+    int error = 0;
+
+    switch (ext) {
+        case CD_EXT_CUE: {
+            psxd_cue_t* cue = psxd_cue_create();
+
+            psxd_cue_init_disc(cue, cdrom->disc);
+            psxd_cue_init(cue);
+
+            error = psxd_cue_load(cue, path);
+
+            if (error)
+                break;
+
+            cdrom_check_cd_type(cdrom);
+        } break;
+
+        case CD_EXT_BIN: {
+            psxd_bin_t* bin = psxd_bin_create();
+
+            psxd_bin_init_disc(bin, cdrom->disc);
+            psxd_bin_init(bin);
+
+            error = psxd_bin_load(bin, path);
+
+            if (error)
+                break;
+
+            cdrom_check_cd_type(cdrom);
+        } break;
+
+        case CD_EXT_UNSUPPORTED: {
+            log_fatal("Unsupported disc format");
+
+            cdrom->cd_type = CDT_UNKNOWN;
+        } break;
+    }
+
+    free(lower);
+
+    if (error) {
+        log_fatal("Error loading file \'%s\'", path);
+
+        exit(1);
+    }
+}
+
+static const int g_spu_pos_adpcm_table[] = {
+    0, +60, +115, +98, +122
+};
+
+static const int g_spu_neg_adpcm_table[] = {
+    0,   0,  -52, -55,  -60
+};
+
+void cdrom_resample_xa_buf(psx_cdrom_t* cdrom, int16_t* dst, int16_t* src, int stereo, int16_t ls) {
+    int f18khz = ((cdrom->xa_sector_buf[0x13] >> 2) & 1) == 1;
+    int sample_count = stereo ? XA_STEREO_SAMPLES : XA_MONO_SAMPLES;
+    int resample_count = stereo ? XA_STEREO_RESAMPLE_SIZE : XA_MONO_RESAMPLE_SIZE;
+
+    resample_count *= f18khz + 1;
+
+    // Nearest neighbor
+    // for (int i = 0; i < sample_count; i++)
+    //     for (int k = 0; k < 7; k++)
+    //         cdrom->xa_upsample_buf[(i*7)+k] = src[i];
+
+    // Linear Upsampling
+    int16_t a = ls;
+    int16_t b = src[0];
+
+    for (int k = 0; k < 7; k++)
+        cdrom->xa_upsample_buf[k] = a + ((k+1)/8) * (b - a);
+
+    for (int i = 1; i < sample_count; i++) {
+        a = b;
+        b = src[i];
+
+        for (int k = 0; k < 7; k++)
+            cdrom->xa_upsample_buf[(i*7)+k] =
+                a + ((k+1)/8) * (b - a);
+    }
+
+    int m = f18khz ? 3 : 6;
+
+    for (int i = 0; i < resample_count; i++)
+        dst[i] = cdrom->xa_upsample_buf[i*m];
+
+    cdrom->xa_remaining_samples = resample_count;
+}
+
+void cdrom_decode_xa_block(psx_cdrom_t* cdrom, int idx, int blk, int nib, int16_t* buf, int16_t* h) {
+    int shift  = 12 - (cdrom->xa_sector_buf[idx + 4 + blk * 2 + nib] & 0x0F);
+    int filter =      (cdrom->xa_sector_buf[idx + 4 + blk * 2 + nib] & 0x30) >> 4;
+
+    int32_t f0 = g_spu_pos_adpcm_table[filter];
+    int32_t f1 = g_spu_neg_adpcm_table[filter];
+
+    for (int j = 0; j < 28; j++) {
+        uint16_t n = (cdrom->xa_sector_buf[idx + 16 + blk + j * 4] >> (nib * 4)) & 0x0f;
+
+        int16_t t = (int16_t)(n << 12) >> 12; 
+        int16_t s = (t << shift) + (((h[0] * f0) + (h[1] * f1) + 32) / 64);
+
+        s = (s < INT16_MIN) ? INT16_MIN : ((s > INT16_MAX) ? INT16_MAX : s);
+
+        h[1] = h[0];
+        h[0] = s;
+
+        buf[j] = s;
+    }
+}
+
+void cdrom_decode_xa_sector(psx_cdrom_t* cdrom, void* buf) {
+    int src = 24;
+
+    int16_t left[28];
+    int16_t right[28];
+    int16_t left_h[2] = { 0, 0 };
+    int16_t right_h[2] = { 0, 0 };
+
+    int16_t* left_ptr = cdrom->xa_left_buf;
+    int16_t* right_ptr = cdrom->xa_right_buf;
+    int16_t* mono_ptr = cdrom->xa_mono_buf;
+
+    for (int i = 0; i < 18; i++) {
+        for (int blk = 0; blk < 4; blk++) {
+            if (cdrom->xa_sector_buf[0x13] & 1) {
+                cdrom_decode_xa_block(cdrom, src, blk, 0, left, 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;
+    }
+}
+
+void cdrom_fetch_xa_sector(psx_cdrom_t* cdrom) {
+    while (true) {
+        if (psx_disc_seek(cdrom->disc, cdrom->xa_msf)) {
+            cdrom->xa_playing = 0;
+
+            return;
+        }
+
+        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] & 4) != 4)
+            continue;
+
+        // If we get here it means this is a real-time audio sector.
+        // If the XA filter is disabled, we're done
+        if (!(cdrom->mode & MODE_XA_FILTER))
+            return;
+
+        // Else check XA file/channel
+        int file_eq = cdrom->xa_sector_buf[0x10] == cdrom->xa_file;
+        int channel_eq = cdrom->xa_sector_buf[0x11] == cdrom->xa_channel;
+
+        // If they are equal to our filter values, we're done
+        // else keep searching
+        if (file_eq && channel_eq)
+            return;
+    }
+}
+
+void psx_cdrom_get_cdda_samples(psx_cdrom_t* cdrom, void* buf, int size, psx_spu_t* spu) {
+    memset(buf, 0, size);
+
+    if (!cdrom->disc)
+        return;
+
+    if (cdrom->xa_playing) {
+        int16_t* ptr = (int16_t*)buf;
+
+        for (int i = 0; i < (size >> 2); i++) {
+            int stereo = (cdrom->xa_sector_buf[0x13] & 1) == 1;
+
+            if (!cdrom->xa_remaining_samples) {
+                cdrom_fetch_xa_sector(cdrom);
+
+                if (cdrom->xa_sector_buf[0x12] & 0x80) {
+                    SET_BITS(status, STAT_ADPBUSY_MASK, 0);
+
+                    cdrom->xa_playing = 0;
+                    cdrom->xa_remaining_samples = 0;
+
+                    return;
+                }
+
+                stereo = (cdrom->xa_sector_buf[0x13] & 1) == 1;
+
+                cdrom_decode_xa_sector(cdrom, buf);
+
+                memset(cdrom->xa_upsample_buf, 0, ((14112 * 2) + 6) * sizeof(int16_t));
+                memset(cdrom->xa_left_resample_buf, 0, (XA_STEREO_RESAMPLE_SIZE * 2) * sizeof(int16_t));
+                memset(cdrom->xa_right_resample_buf, 0, (XA_STEREO_RESAMPLE_SIZE * 2) * sizeof(int16_t));
+
+                if (stereo) {
+                    cdrom_resample_xa_buf(cdrom, cdrom->xa_left_resample_buf, cdrom->xa_left_buf, stereo, cdrom->xa_last_left_sample);
+                    cdrom_resample_xa_buf(cdrom, cdrom->xa_right_resample_buf, cdrom->xa_right_buf, stereo, cdrom->xa_last_right_sample);
+                } else {
+                    cdrom_resample_xa_buf(cdrom, cdrom->xa_mono_resample_buf, cdrom->xa_mono_buf, stereo, cdrom->xa_last_mono_sample);
+                }
+
+                cdrom->xa_sample_idx = 0;
+            }
+
+            if (stereo) {
+                cdrom->xa_last_left_sample = cdrom->xa_left_resample_buf[cdrom->xa_sample_idx];
+                cdrom->xa_last_right_sample = cdrom->xa_right_resample_buf[cdrom->xa_sample_idx++];
+
+                *ptr++ = cdrom->xa_last_left_sample;
+                *ptr++ = cdrom->xa_last_right_sample;
+
+            } else {
+                cdrom->xa_last_mono_sample = cdrom->xa_mono_resample_buf[cdrom->xa_sample_idx++];
+
+                *ptr++ = cdrom->xa_last_mono_sample;
+                *ptr++ = cdrom->xa_last_mono_sample;
+            }
+
+            --cdrom->xa_remaining_samples;
+        }
+
+        return;
+    }
+
+    if (!cdrom->cdda_playing) {
+        memset(buf, 0, size);
+    
+        return;
+    }
+
+    // Seek to that address and read sector
+    if (psx_disc_seek(cdrom->disc, cdrom->cdda_msf))
+        cdrom->cdda_playing = 0;
+
+    psx_disc_read_sector(cdrom->disc, cdrom->cdda_buf);
+
+    ++cdrom->cdda_sectors_played;
+
+    // Increment sector
+    msf_add_f(&cdrom->cdda_msf, 1);
+
+    memcpy(buf, cdrom->cdda_buf, size);
+
+    psx_spu_update_cdda_buffer(spu, cdrom->cdda_buf);
+
+    // Handle report IRQ
+    if (cdrom->cdda_sectors_played == CD_SECTORS_PS) {
+        if (cdrom->mode & MODE_REPORT) {
+            SET_BITS(ifr, IFR_INT, 1);
+
+            msf_t track, current = cdrom->cdda_msf;
+
+            msf_from_bcd(&current);
+
+            psx_disc_get_track_addr(cdrom->disc, &track, cdrom->cdda_track);
+
+            unsigned int track_s = (track.m * 60) + track.s;
+            unsigned int current_s = (current.m * 60) + current.s;
+            unsigned int diff = current_s - track_s;
+
+            current.s = diff;
+            current.m = 0;
+
+            msf_adjust(&current);
+            //msf_to_bcd(&current);
+
+            RESP_PUSH(0);
+            RESP_PUSH(0);
+            RESP_PUSH(cdrom->cdda_msf.f);
+            RESP_PUSH(current.s | 0x80);
+            RESP_PUSH(current.m);
+            RESP_PUSH(0);
+            RESP_PUSH(cdrom->cdda_track);
+            RESP_PUSH(GETSTAT_PLAY);
+
+            psx_ic_irq(cdrom->ic, IC_CDROM);
+        }
+
+        cdrom->cdda_sectors_played = 0;
+    }
+}
+
+void psx_cdrom_destroy(psx_cdrom_t* cdrom) {
+    if (cdrom->disc)
+        psx_disc_destroy(cdrom->disc);
+
+    free(cdrom);
 }
\ No newline at end of file
--- a/psx/dev/cdrom.h
+++ b/psx/dev/cdrom.h
@@ -1,324 +1,327 @@
-#ifndef CDROM_H
-#define CDROM_H
-
-#include <stdint.h>
-#include <stdio.h>
-
-#include "ic.h"
-#include "../disc.h"
-#include "../disc/cue.h"
-#include "../disc/bin.h"
-#include "../msf.h"
-#include "spu.h"
-
-// #define DELAY_1MS (0xc4e1)
-// #define READ_SINGLE_DELAY (0x6e1cd)
-// #define READ_DOUBLE_DELAY (0x36cd2)
-#define DELAY_1MS (PSX_CPU_CPS / 1000)
-#define READ_SINGLE_DELAY (PSX_CPU_CPS / 75)
-#define READ_DOUBLE_DELAY (PSX_CPU_CPS / (2 * 75))
-
-#define PSX_CDROM_BEGIN 0x1f801800
-#define PSX_CDROM_SIZE  0x4
-#define PSX_CDROM_END   0x1f801803
-
-enum {
-    CD_STATE_RECV_CMD,
-    CD_STATE_SEND_RESP1,
-    CD_STATE_SEND_RESP2,
-    CD_STATE_ERROR
-};
-
-#define CDL_NONE        0x00
-#define CDL_GETSTAT     0x01
-#define CDL_SETLOC      0x02
-#define CDL_PLAY        0x03
-#define CDL_FORWARD     0x04
-#define CDL_BACKWARD    0x05
-#define CDL_READN       0x06
-#define CDL_MOTORON     0x07
-#define CDL_STOP        0x08
-#define CDL_PAUSE       0x09
-#define CDL_INIT        0x0a
-#define CDL_MUTE        0x0b
-#define CDL_DEMUTE      0x0c
-#define CDL_SETFILTER   0x0d
-#define CDL_SETMODE     0x0e
-#define CDL_GETPARAM    0x0f
-#define CDL_GETLOCL     0x10
-#define CDL_GETLOCP     0x11
-#define CDL_SETSESSION  0x12
-#define CDL_GETTN       0x13
-#define CDL_GETTD       0x14
-#define CDL_SEEKL       0x15
-#define CDL_SEEKP       0x16
-#define CDL_TEST        0x19
-#define CDL_GETID       0x1a
-#define CDL_READS       0x1b
-#define CDL_RESET       0x1c
-#define CDL_GETQ        0x1d
-#define CDL_READTOC     0x1e
-#define CDL_VIDEOCD     0x1f
-#define CDL_ERROR       0x20
-
-#define STAT_INDEX_MASK   0x3
-#define STAT_ADPBUSY_MASK 0x4
-#define STAT_PRMEMPT_MASK 0x8
-#define STAT_PRMWRDY_MASK 0x10
-#define STAT_RSLRRDY_MASK 0x20
-#define STAT_DRQSTS_MASK  0x40
-#define STAT_BUSYSTS_MASK 0x80
-#define STAT_INDEX   (cdrom->status & STAT_INDEX_MASK)
-#define STAT_ADPBUSY (cdrom->status & STAT_ADPBUSY_MASK)
-#define STAT_PRMEMPT (cdrom->status & STAT_PRMEMPT_MASK)
-#define STAT_PRMWRDY (cdrom->status & STAT_PRMWRDY_MASK)
-#define STAT_RSLRRDY (cdrom->status & STAT_RSLRRDY_MASK)
-#define STAT_DRQSTS  (cdrom->status & STAT_DRQSTS_MASK)
-#define STAT_BUSYSTS (cdrom->status & STAT_BUSYSTS_MASK)
-#define SET_BITS(reg, mask, v) { cdrom->reg &= ~mask; cdrom->reg |= v & mask; }
-#define IFR_INT  0x07
-#define IFR_INT1 0x01
-#define IFR_INT2 0x02
-#define IFR_INT3 0x03
-#define IFR_INT4 0x04
-#define IFR_INT5 0x05
-#define IFR_INT6 0x06
-#define IFR_INT7 0x07
-
-#define GETSTAT_ERROR      0x01
-#define GETSTAT_MOTOR      0x02
-#define GETSTAT_SEEKERROR  0x04
-#define GETSTAT_IDERROR    0x08
-#define GETSTAT_TRAYOPEN   0x10
-#define GETSTAT_READ       0x20
-#define GETSTAT_SEEK       0x40
-#define GETSTAT_PLAY       0x80
-
-/*
-  7   Speed       (0=Normal speed, 1=Double speed)
-  6   XA-ADPCM    (0=Off, 1=Send XA-ADPCM sectors to SPU Audio Input)
-  5   Sector Size (0=800h=DataOnly, 1=924h=WholeSectorExceptSyncBytes)
-  4   Ignore Bit  (0=Normal, 1=Ignore Sector Size and Setloc position)
-  3   XA-Filter   (0=Off, 1=Process only XA-ADPCM sectors that match Setfilter)
-  2   Report      (0=Off, 1=Enable Report-Interrupts for Audio Play)
-  1   AutoPause   (0=Off, 1=Auto Pause upon End of Track) ;for Audio Play
-  0   CDDA        (0=Off, 1=Allow to Read CD-DA Sectors; ignore missing EDC)
-*/
-
-#define MODE_CDDA           0x01
-#define MODE_AUTOPAUSE      0x02
-#define MODE_REPORT         0x04
-#define MODE_XA_FILTER      0x08
-#define MODE_IGNORE         0x10
-#define MODE_SECTOR_SIZE    0x20
-#define MODE_XA_ADPCM       0x40
-#define MODE_SPEED          0x80
-
-/*
-  0-4 0    Not used (should be zero)
-  5   SMEN Want Command Start Interrupt on Next Command (0=No change, 1=Yes)
-  6   BFWR ...
-  7   BFRD Want Data         (0=No/Reset Data Fifo, 1=Yes/Load Data Fifo)
-*/
-
-#define REQ_SMEN 0x20
-#define REQ_BFWR 0x40
-#define REQ_BFRD 0x80
-
-/*
-  ___These values appear in the FIRST response; with stat.bit0 set___
-  10h - Invalid Sub_function (for command 19h), or invalid parameter value
-  20h - Wrong number of parameters
-  40h - Invalid command
-  80h - Cannot respond yet (eg. required info was not yet read from disk yet)
-           (namely, TOC not-yet-read or so)
-           (also appears if no disk inserted at all)
-  ___These values appear in the SECOND response; with stat.bit2 set___
-  04h - Seek failed (when trying to use SeekL on Audio CDs)
-  ___These values appear even if no command was sent; with stat.bit2 set___
-  08h - Drive door became opened
-*/
-
-#define ERR_INVSUBF 0x10
-#define ERR_PCOUNT  0x20
-#define ERR_INVALID 0x40
-#define ERR_BUSY    0x80
-#define ERR_SEEK    0x04
-#define ERR_LIDOPEN 0x08
-
-enum {
-    CDT_LICENSED,
-    CDT_AUDIO,
-    CDT_UNKNOWN
-};
-
-typedef struct {
-    uint32_t bus_delay;
-    uint32_t io_base, io_size;
-
-    psx_ic_t* ic;
-
-    uint8_t status;
-    uint8_t ifr;
-    uint8_t ier;
-
-    uint8_t pfifo[16];
-    uint8_t rfifo[16];
-    int pfifo_index;
-    int rfifo_index;
-
-    uint8_t* read_buf;
-    uint8_t* dfifo;
-    int dfifo_index;
-    int dfifo_full;
-
-    // GetStat bits
-    uint8_t stat;
-
-    // API
-    int tray_open;
-
-    // Setloc
-    msf_t seek_msf;
-    uint32_t seek_offset;
-    int seek_pending;
-
-    // Setmode
-    uint8_t mode;
-
-    int irq_delay;
-    int irq_disable;
-    uint8_t command;
-    uint8_t delayed_command;
-    uint8_t int_number;
-    int state;
-    int delayed_response;
-    int spin_delay;
-    uint8_t error;
-    uint8_t error_flags;
-    int ongoing_read_command;
-    int gettd_track;
-
-    // CDDA
-    uint8_t* cdda_buf;
-    int cdda_sector_offset;
-    msf_t cdda_msf;
-    int cdda_playing;
-    int cdda_sectors_played;
-    int cdda_track;
-
-    // XA-ADPCM
-    uint8_t* xa_sector_buf;
-    msf_t xa_msf;
-    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;
-    uint32_t xa_sample_idx;
-    int xa_remaining_samples;
-    uint32_t xa_step;
-    uint32_t xa_ringbuf_pos;
-    int16_t* xa_left_resample_buf;
-    int16_t* xa_right_resample_buf;
-    int16_t* xa_mono_resample_buf;
-    int16_t* xa_upsample_buf;
-
-    const char* path;
-    psx_disc_t* disc;
-
-    int cd_type;
-} psx_cdrom_t;
-
-psx_cdrom_t* psx_cdrom_create();
-void psx_cdrom_init(psx_cdrom_t*, psx_ic_t*);
-uint32_t psx_cdrom_read32(psx_cdrom_t*, uint32_t);
-uint16_t psx_cdrom_read16(psx_cdrom_t*, uint32_t);
-uint8_t psx_cdrom_read8(psx_cdrom_t*, uint32_t);
-void psx_cdrom_write32(psx_cdrom_t*, uint32_t, uint32_t);
-void psx_cdrom_write16(psx_cdrom_t*, uint32_t, uint16_t);
-void psx_cdrom_write8(psx_cdrom_t*, uint32_t, uint8_t);
-void psx_cdrom_update(psx_cdrom_t*, int);
-void psx_cdrom_destroy(psx_cdrom_t*);
-void psx_cdrom_open(psx_cdrom_t*, const char*);
-void psx_cdrom_get_cdda_samples(psx_cdrom_t*, void*, int, psx_spu_t*);
-
-/*
-  Command          Parameters      Response(s)
-  00h -            -               INT5(11h,40h)  ;reportedly "Sync" uh?
-  01h Getstat      -               INT3(stat)
-  02h Setloc     E amm,ass,asect   INT3(stat)
-  03h Play       E (track)         INT3(stat), optional INT1(report bytes)
-  04h Forward    E -               INT3(stat), optional INT1(report bytes)
-  05h Backward   E -               INT3(stat), optional INT1(report bytes)
-  06h ReadN      E -               INT3(stat), INT1(stat), datablock
-  07h MotorOn    E -               INT3(stat), INT2(stat)
-  08h Stop       E -               INT3(stat), INT2(stat)
-  09h Pause      E -               INT3(stat), INT2(stat)
-  0Ah Init         -               INT3(late-stat), INT2(stat)
-  0Bh Mute       E -               INT3(stat)
-  0Ch Demute     E -               INT3(stat)
-  0Dh Setfilter  E file,channel    INT3(stat)
-  0Eh Setmode      mode            INT3(stat)
-  0Fh Getparam     -               INT3(stat,mode,null,file,channel)
-  10h GetlocL    E -               INT3(amm,ass,asect,mode,file,channel,sm,ci)
-  11h GetlocP    E -               INT3(track,index,mm,ss,sect,amm,ass,asect)
-  12h SetSession E session         INT3(stat), INT2(stat)
-  13h GetTN      E -               INT3(stat,first,last)  ;BCD
-  14h GetTD      E track (BCD)     INT3(stat,mm,ss)       ;BCD
-  15h SeekL      E -               INT3(stat), INT2(stat)  ;\use prior Setloc
-  16h SeekP      E -               INT3(stat), INT2(stat)  ;/to set target
-  17h -            -               INT5(11h,40h)  ;reportedly "SetClock" uh?
-  18h -            -               INT5(11h,40h)  ;reportedly "GetClock" uh?
-  19h Test         sub_function    depends on sub_function (see below)
-  1Ah GetID      E -               INT3(stat), INT2/5(stat,flg,typ,atip,"SCEx")
-  1Bh ReadS      E?-               INT3(stat), INT1(stat), datablock
-  1Ch Reset        -               INT3(stat), Delay            ;-not DTL-H2000
-  1Dh GetQ       E adr,point       INT3(stat), INT2(10bytesSubQ,peak_lo) ;\not
-  1Eh ReadTOC      -               INT3(late-stat), INT2(stat)           ;/vC0
-  1Fh VideoCD      sub,a,b,c,d,e   INT3(stat,a,b,c,d,e)   ;<-- SCPH-5903 only
-  1Fh..4Fh -       -               INT5(11h,40h)  ;-Unused/invalid
-  50h Secret 1     -               INT5(11h,40h)  ;\
-  51h Secret 2     "Licensed by"   INT5(11h,40h)  ;
-  52h Secret 3     "Sony"          INT5(11h,40h)  ; Secret Unlock Commands
-  53h Secret 4     "Computer"      INT5(11h,40h)  ; (not in version vC0, and,
-  54h Secret 5     "Entertainment" INT5(11h,40h)  ; nonfunctional in japan)
-  55h Secret 6     "<region>"      INT5(11h,40h)  ;
-  56h Secret 7     -               INT5(11h,40h)  ;/
-  57h SecretLock   -               INT5(11h,40h)  ;-Secret Lock Command
-  58h..5Fh Crash   -               Crashes the HC05 (jumps into a data area)
-  6Fh..FFh -       -               INT5(11h,40h)  ;-Unused/invalid
-*/
-
-void cdrom_cmd_unimplemented(psx_cdrom_t*);
-void cdrom_cmd_getstat(psx_cdrom_t*);
-void cdrom_cmd_setloc(psx_cdrom_t*);
-void cdrom_cmd_play(psx_cdrom_t*);
-void cdrom_cmd_readn(psx_cdrom_t*);
-void cdrom_cmd_motoron(psx_cdrom_t*);
-void cdrom_cmd_stop(psx_cdrom_t*);
-void cdrom_cmd_pause(psx_cdrom_t*);
-void cdrom_cmd_init(psx_cdrom_t*);
-void cdrom_cmd_unmute(psx_cdrom_t*);
-void cdrom_cmd_setfilter(psx_cdrom_t*);
-void cdrom_cmd_setmode(psx_cdrom_t*);
-void cdrom_cmd_getparam(psx_cdrom_t*);
-void cdrom_cmd_getlocl(psx_cdrom_t*);
-void cdrom_cmd_getlocp(psx_cdrom_t*);
-void cdrom_cmd_setsession(psx_cdrom_t*);
-void cdrom_cmd_gettn(psx_cdrom_t*);
-void cdrom_cmd_gettd(psx_cdrom_t*);
-void cdrom_cmd_seekl(psx_cdrom_t*);
-void cdrom_cmd_seekp(psx_cdrom_t*);
-void cdrom_cmd_test(psx_cdrom_t*);
-void cdrom_cmd_getid(psx_cdrom_t*);
-void cdrom_cmd_reads(psx_cdrom_t*);
-void cdrom_cmd_readtoc(psx_cdrom_t*);
-void cdrom_cmd_videocd(psx_cdrom_t*);
-
+#ifndef CDROM_H
+#define CDROM_H
+
+#include <stdint.h>
+#include <stdio.h>
+
+#include "ic.h"
+#include "../disc.h"
+#include "../disc/cue.h"
+#include "../disc/bin.h"
+#include "../msf.h"
+#include "spu.h"
+
+// #define DELAY_1MS (0xc4e1)
+// #define READ_SINGLE_DELAY (0x6e1cd)
+// #define READ_DOUBLE_DELAY (0x36cd2)
+#define DELAY_1MS (PSX_CPU_CPS / 1000)
+#define READ_SINGLE_DELAY (PSX_CPU_CPS / 75)
+#define READ_DOUBLE_DELAY (PSX_CPU_CPS / (2 * 75))
+
+#define PSX_CDROM_BEGIN 0x1f801800
+#define PSX_CDROM_SIZE  0x4
+#define PSX_CDROM_END   0x1f801803
+
+enum {
+    CD_STATE_RECV_CMD,
+    CD_STATE_SEND_RESP1,
+    CD_STATE_SEND_RESP2,
+    CD_STATE_ERROR
+};
+
+#define CDL_NONE        0x00
+#define CDL_GETSTAT     0x01
+#define CDL_SETLOC      0x02
+#define CDL_PLAY        0x03
+#define CDL_FORWARD     0x04
+#define CDL_BACKWARD    0x05
+#define CDL_READN       0x06
+#define CDL_MOTORON     0x07
+#define CDL_STOP        0x08
+#define CDL_PAUSE       0x09
+#define CDL_INIT        0x0a
+#define CDL_MUTE        0x0b
+#define CDL_DEMUTE      0x0c
+#define CDL_SETFILTER   0x0d
+#define CDL_SETMODE     0x0e
+#define CDL_GETPARAM    0x0f
+#define CDL_GETLOCL     0x10
+#define CDL_GETLOCP     0x11
+#define CDL_SETSESSION  0x12
+#define CDL_GETTN       0x13
+#define CDL_GETTD       0x14
+#define CDL_SEEKL       0x15
+#define CDL_SEEKP       0x16
+#define CDL_TEST        0x19
+#define CDL_GETID       0x1a
+#define CDL_READS       0x1b
+#define CDL_RESET       0x1c
+#define CDL_GETQ        0x1d
+#define CDL_READTOC     0x1e
+#define CDL_VIDEOCD     0x1f
+#define CDL_ERROR       0x20
+
+#define STAT_INDEX_MASK   0x3
+#define STAT_ADPBUSY_MASK 0x4
+#define STAT_PRMEMPT_MASK 0x8
+#define STAT_PRMWRDY_MASK 0x10
+#define STAT_RSLRRDY_MASK 0x20
+#define STAT_DRQSTS_MASK  0x40
+#define STAT_BUSYSTS_MASK 0x80
+#define STAT_INDEX   (cdrom->status & STAT_INDEX_MASK)
+#define STAT_ADPBUSY (cdrom->status & STAT_ADPBUSY_MASK)
+#define STAT_PRMEMPT (cdrom->status & STAT_PRMEMPT_MASK)
+#define STAT_PRMWRDY (cdrom->status & STAT_PRMWRDY_MASK)
+#define STAT_RSLRRDY (cdrom->status & STAT_RSLRRDY_MASK)
+#define STAT_DRQSTS  (cdrom->status & STAT_DRQSTS_MASK)
+#define STAT_BUSYSTS (cdrom->status & STAT_BUSYSTS_MASK)
+#define SET_BITS(reg, mask, v) { cdrom->reg &= ~mask; cdrom->reg |= v & mask; }
+#define IFR_INT  0x07
+#define IFR_INT1 0x01
+#define IFR_INT2 0x02
+#define IFR_INT3 0x03
+#define IFR_INT4 0x04
+#define IFR_INT5 0x05
+#define IFR_INT6 0x06
+#define IFR_INT7 0x07
+
+#define GETSTAT_ERROR      0x01
+#define GETSTAT_MOTOR      0x02
+#define GETSTAT_SEEKERROR  0x04
+#define GETSTAT_IDERROR    0x08
+#define GETSTAT_TRAYOPEN   0x10
+#define GETSTAT_READ       0x20
+#define GETSTAT_SEEK       0x40
+#define GETSTAT_PLAY       0x80
+
+/*
+  7   Speed       (0=Normal speed, 1=Double speed)
+  6   XA-ADPCM    (0=Off, 1=Send XA-ADPCM sectors to SPU Audio Input)
+  5   Sector Size (0=800h=DataOnly, 1=924h=WholeSectorExceptSyncBytes)
+  4   Ignore Bit  (0=Normal, 1=Ignore Sector Size and Setloc position)
+  3   XA-Filter   (0=Off, 1=Process only XA-ADPCM sectors that match Setfilter)
+  2   Report      (0=Off, 1=Enable Report-Interrupts for Audio Play)
+  1   AutoPause   (0=Off, 1=Auto Pause upon End of Track) ;for Audio Play
+  0   CDDA        (0=Off, 1=Allow to Read CD-DA Sectors; ignore missing EDC)
+*/
+
+#define MODE_CDDA           0x01
+#define MODE_AUTOPAUSE      0x02
+#define MODE_REPORT         0x04
+#define MODE_XA_FILTER      0x08
+#define MODE_IGNORE         0x10
+#define MODE_SECTOR_SIZE    0x20
+#define MODE_XA_ADPCM       0x40
+#define MODE_SPEED          0x80
+
+/*
+  0-4 0    Not used (should be zero)
+  5   SMEN Want Command Start Interrupt on Next Command (0=No change, 1=Yes)
+  6   BFWR ...
+  7   BFRD Want Data         (0=No/Reset Data Fifo, 1=Yes/Load Data Fifo)
+*/
+
+#define REQ_SMEN 0x20
+#define REQ_BFWR 0x40
+#define REQ_BFRD 0x80
+
+/*
+  ___These values appear in the FIRST response; with stat.bit0 set___
+  10h - Invalid Sub_function (for command 19h), or invalid parameter value
+  20h - Wrong number of parameters
+  40h - Invalid command
+  80h - Cannot respond yet (eg. required info was not yet read from disk yet)
+           (namely, TOC not-yet-read or so)
+           (also appears if no disk inserted at all)
+  ___These values appear in the SECOND response; with stat.bit2 set___
+  04h - Seek failed (when trying to use SeekL on Audio CDs)
+  ___These values appear even if no command was sent; with stat.bit2 set___
+  08h - Drive door became opened
+*/
+
+#define ERR_INVSUBF 0x10
+#define ERR_PCOUNT  0x20
+#define ERR_INVALID 0x40
+#define ERR_BUSY    0x80
+#define ERR_SEEK    0x04
+#define ERR_LIDOPEN 0x08
+
+enum {
+    CDT_LICENSED,
+    CDT_AUDIO,
+    CDT_UNKNOWN
+};
+
+typedef struct {
+    uint32_t bus_delay;
+    uint32_t io_base, io_size;
+
+    psx_ic_t* ic;
+
+    uint8_t status;
+    uint8_t ifr;
+    uint8_t ier;
+
+    uint8_t pfifo[16];
+    uint8_t rfifo[16];
+    int pfifo_index;
+    int rfifo_index;
+
+    uint8_t* read_buf;
+    uint8_t* dfifo;
+    int dfifo_index;
+    int dfifo_full;
+
+    // GetStat bits
+    uint8_t stat;
+
+    // API
+    int tray_open;
+
+    // Setloc
+    msf_t seek_msf;
+    uint32_t seek_offset;
+    int seek_pending;
+
+    // Setmode
+    uint8_t mode;
+
+    int irq_delay;
+    int irq_disable;
+    uint8_t command;
+    uint8_t delayed_command;
+    uint8_t int_number;
+    int state;
+    int delayed_response;
+    int spin_delay;
+    uint8_t error;
+    uint8_t error_flags;
+    int ongoing_read_command;
+    int gettd_track;
+
+    // CDDA
+    uint8_t* cdda_buf;
+    int cdda_sector_offset;
+    msf_t cdda_msf;
+    int cdda_playing;
+    int cdda_sectors_played;
+    int cdda_track;
+
+    // XA-ADPCM
+    uint8_t* xa_sector_buf;
+    msf_t xa_msf;
+    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;
+    uint32_t xa_sample_idx;
+    int xa_remaining_samples;
+    uint32_t xa_step;
+    uint32_t xa_ringbuf_pos;
+    int16_t* xa_left_resample_buf;
+    int16_t* xa_right_resample_buf;
+    int16_t* xa_mono_resample_buf;
+    int16_t* xa_upsample_buf;
+    int16_t xa_last_left_sample;
+    int16_t xa_last_right_sample;
+    int16_t xa_last_mono_sample;
+
+    const char* path;
+    psx_disc_t* disc;
+
+    int cd_type;
+} psx_cdrom_t;
+
+psx_cdrom_t* psx_cdrom_create();
+void psx_cdrom_init(psx_cdrom_t*, psx_ic_t*);
+uint32_t psx_cdrom_read32(psx_cdrom_t*, uint32_t);
+uint16_t psx_cdrom_read16(psx_cdrom_t*, uint32_t);
+uint8_t psx_cdrom_read8(psx_cdrom_t*, uint32_t);
+void psx_cdrom_write32(psx_cdrom_t*, uint32_t, uint32_t);
+void psx_cdrom_write16(psx_cdrom_t*, uint32_t, uint16_t);
+void psx_cdrom_write8(psx_cdrom_t*, uint32_t, uint8_t);
+void psx_cdrom_update(psx_cdrom_t*, int);
+void psx_cdrom_destroy(psx_cdrom_t*);
+void psx_cdrom_open(psx_cdrom_t*, const char*);
+void psx_cdrom_get_cdda_samples(psx_cdrom_t*, void*, int, psx_spu_t*);
+
+/*
+  Command          Parameters      Response(s)
+  00h -            -               INT5(11h,40h)  ;reportedly "Sync" uh?
+  01h Getstat      -               INT3(stat)
+  02h Setloc     E amm,ass,asect   INT3(stat)
+  03h Play       E (track)         INT3(stat), optional INT1(report bytes)
+  04h Forward    E -               INT3(stat), optional INT1(report bytes)
+  05h Backward   E -               INT3(stat), optional INT1(report bytes)
+  06h ReadN      E -               INT3(stat), INT1(stat), datablock
+  07h MotorOn    E -               INT3(stat), INT2(stat)
+  08h Stop       E -               INT3(stat), INT2(stat)
+  09h Pause      E -               INT3(stat), INT2(stat)
+  0Ah Init         -               INT3(late-stat), INT2(stat)
+  0Bh Mute       E -               INT3(stat)
+  0Ch Demute     E -               INT3(stat)
+  0Dh Setfilter  E file,channel    INT3(stat)
+  0Eh Setmode      mode            INT3(stat)
+  0Fh Getparam     -               INT3(stat,mode,null,file,channel)
+  10h GetlocL    E -               INT3(amm,ass,asect,mode,file,channel,sm,ci)
+  11h GetlocP    E -               INT3(track,index,mm,ss,sect,amm,ass,asect)
+  12h SetSession E session         INT3(stat), INT2(stat)
+  13h GetTN      E -               INT3(stat,first,last)  ;BCD
+  14h GetTD      E track (BCD)     INT3(stat,mm,ss)       ;BCD
+  15h SeekL      E -               INT3(stat), INT2(stat)  ;\use prior Setloc
+  16h SeekP      E -               INT3(stat), INT2(stat)  ;/to set target
+  17h -            -               INT5(11h,40h)  ;reportedly "SetClock" uh?
+  18h -            -               INT5(11h,40h)  ;reportedly "GetClock" uh?
+  19h Test         sub_function    depends on sub_function (see below)
+  1Ah GetID      E -               INT3(stat), INT2/5(stat,flg,typ,atip,"SCEx")
+  1Bh ReadS      E?-               INT3(stat), INT1(stat), datablock
+  1Ch Reset        -               INT3(stat), Delay            ;-not DTL-H2000
+  1Dh GetQ       E adr,point       INT3(stat), INT2(10bytesSubQ,peak_lo) ;\not
+  1Eh ReadTOC      -               INT3(late-stat), INT2(stat)           ;/vC0
+  1Fh VideoCD      sub,a,b,c,d,e   INT3(stat,a,b,c,d,e)   ;<-- SCPH-5903 only
+  1Fh..4Fh -       -               INT5(11h,40h)  ;-Unused/invalid
+  50h Secret 1     -               INT5(11h,40h)  ;\
+  51h Secret 2     "Licensed by"   INT5(11h,40h)  ;
+  52h Secret 3     "Sony"          INT5(11h,40h)  ; Secret Unlock Commands
+  53h Secret 4     "Computer"      INT5(11h,40h)  ; (not in version vC0, and,
+  54h Secret 5     "Entertainment" INT5(11h,40h)  ; nonfunctional in japan)
+  55h Secret 6     "<region>"      INT5(11h,40h)  ;
+  56h Secret 7     -               INT5(11h,40h)  ;/
+  57h SecretLock   -               INT5(11h,40h)  ;-Secret Lock Command
+  58h..5Fh Crash   -               Crashes the HC05 (jumps into a data area)
+  6Fh..FFh -       -               INT5(11h,40h)  ;-Unused/invalid
+*/
+
+void cdrom_cmd_unimplemented(psx_cdrom_t*);
+void cdrom_cmd_getstat(psx_cdrom_t*);
+void cdrom_cmd_setloc(psx_cdrom_t*);
+void cdrom_cmd_play(psx_cdrom_t*);
+void cdrom_cmd_readn(psx_cdrom_t*);
+void cdrom_cmd_motoron(psx_cdrom_t*);
+void cdrom_cmd_stop(psx_cdrom_t*);
+void cdrom_cmd_pause(psx_cdrom_t*);
+void cdrom_cmd_init(psx_cdrom_t*);
+void cdrom_cmd_unmute(psx_cdrom_t*);
+void cdrom_cmd_setfilter(psx_cdrom_t*);
+void cdrom_cmd_setmode(psx_cdrom_t*);
+void cdrom_cmd_getparam(psx_cdrom_t*);
+void cdrom_cmd_getlocl(psx_cdrom_t*);
+void cdrom_cmd_getlocp(psx_cdrom_t*);
+void cdrom_cmd_setsession(psx_cdrom_t*);
+void cdrom_cmd_gettn(psx_cdrom_t*);
+void cdrom_cmd_gettd(psx_cdrom_t*);
+void cdrom_cmd_seekl(psx_cdrom_t*);
+void cdrom_cmd_seekp(psx_cdrom_t*);
+void cdrom_cmd_test(psx_cdrom_t*);
+void cdrom_cmd_getid(psx_cdrom_t*);
+void cdrom_cmd_reads(psx_cdrom_t*);
+void cdrom_cmd_readtoc(psx_cdrom_t*);
+void cdrom_cmd_videocd(psx_cdrom_t*);
+
 #endif
\ No newline at end of file
--- a/psx/dev/dma.c
+++ b/psx/dev/dma.c
@@ -1,528 +1,543 @@
-#include "dma.h"
-#include "../log.h"
-
-#include <stdint.h>
-#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));
-}
-
-const uint32_t g_psx_dma_ctrl_hw_1_table[] = {
-    0x00000000, 0x00000000, 0x00000000,
-    0x00000000, 0x00000000, 0x00000000,
-    0x00000002
-};
-
-const uint32_t g_psx_dma_ctrl_hw_0_table[] = {
-    0x71770703, 0x71770703, 0x71770703,
-    0x71770703, 0x71770703, 0x71770703,
-    0x50000002
-};
-
-const psx_dma_do_fn_t g_psx_dma_do_table[] = {
-    psx_dma_do_mdec_in,
-    psx_dma_do_mdec_out,
-    psx_dma_do_gpu,
-    psx_dma_do_cdrom,
-    psx_dma_do_spu,
-    psx_dma_do_pio,
-    psx_dma_do_otc
-};
-
-#define CR(c, r) *((&dma->mdec_in.madr) + (c * 3) + r)
-
-void psx_dma_init(psx_dma_t* dma, psx_bus_t* bus, psx_ic_t* ic) {
-    memset(dma, 0, sizeof(psx_dma_t));
-
-    dma->io_base = PSX_DMAR_BEGIN;
-    dma->io_size = PSX_DMAR_SIZE;
-
-    dma->bus = bus;
-    dma->ic = ic;
-
-    dma->dpcr = 0x07654321;
-}
-
-uint32_t psx_dma_read32(psx_dma_t* dma, uint32_t offset) {
-    if (offset < 0x70) {
-        int channel = (offset >> 4) & 0x7;
-        int reg = (offset >> 2) & 0x3;
-        uint32_t cr = CR(channel, reg);
-
-        if (reg == 2) {
-            cr |= g_psx_dma_ctrl_hw_1_table[channel];
-            cr &= g_psx_dma_ctrl_hw_0_table[channel];
-        }
-
-        log_error("DMA channel %u register %u (%08x) read %08x", channel, reg, PSX_DMAR_BEGIN + offset, cr);
-        
-        return cr;
-    } else {
-        switch (offset) {
-            case 0x70: log_error("DMA control read %08x", dma->dpcr); return dma->dpcr;
-            case 0x74: log_error("DMA irqc    read %08x", dma->dicr); return dma->dicr;
-
-            default: {
-                log_error("Unhandled 32-bit DMA read at offset %08x", offset);
-
-                return 0x0;
-            }
-        }
-    }
-}
-
-uint16_t psx_dma_read16(psx_dma_t* dma, uint32_t offset) {
-    switch (offset) {
-        case 0x70: return dma->dpcr;
-        case 0x74: return dma->dicr;
-
-        default: {
-            log_error("Unhandled 16-bit DMA read at offset %08x", offset);
-
-            return 0x0;
-        }
-    }
-}
-
-uint8_t psx_dma_read8(psx_dma_t* dma, uint32_t offset) {
-    switch (offset) {
-        case 0x70: return dma->dpcr;
-        case 0x74: return dma->dicr;
-
-        default: {
-            log_error("Unhandled 8-bit DMA read at offset %08x", offset);
-
-            return 0x0;
-        }
-    }
-}
-
-void psx_dma_write32(psx_dma_t* dma, uint32_t offset, uint32_t value) {
-    if (offset < 0x70) {
-        int channel = (offset >> 4) & 0x7;
-        int reg = (offset >> 2) & 0x3;
-
-        CR(channel, reg) = value;
-
-        log_error("DMA channel %u register %u write (%08x) %08x", channel, reg, PSX_DMAR_BEGIN + offset, value);
-
-        if (reg == 2)
-            g_psx_dma_do_table[channel](dma);
-    } else {
-        switch (offset) {
-            case 0x70: log_error("DMA control write %08x", value); dma->dpcr = value; break;
-            case 0x74: {
-                uint32_t ack = value & DICR_FLAGS;
-                uint32_t flags = dma->dicr & DICR_FLAGS;
-
-                flags &= (~ack);
-                flags &= DICR_FLAGS;
-
-                dma->dicr &= 0x80000000;
-                dma->dicr |= flags;
-                dma->dicr |= value & 0xffffff;
-            } break;
-
-            default: {
-                log_error("Unhandled 32-bit DMA write at offset %08x (%08x)", offset, value);
-            } break;
-        }
-    }
-}
-
-void psx_dma_write16(psx_dma_t* dma, uint32_t offset, uint16_t value) {
-    switch (offset) {
-        default: {
-            log_fatal("Unhandled 16-bit DMA write at offset %08x (%04x)", offset, value);
-
-            //exit(1);
-        } break;
-    }
-}
-
-void psx_dma_write8(psx_dma_t* dma, uint32_t offset, uint8_t value) {
-    switch (offset) {
-        default: {
-            log_fatal("Unhandled 8-bit DMA write at offset %08x (%02x)", offset, value);
-
-            //exit(1);
-        } break;
-    }
-}
-
-const char* g_psx_dma_sync_type_name_table[] = {
-    "burst",
-    "request",
-    "linked",
-    "reserved"
-};
-
-void psx_dma_do_mdec_in(psx_dma_t* dma) {
-    if (!CHCR_BUSY(mdec_in))
-        return;
-
-    // printf("dma mdec in size=%04x bcnt=%04x step=%u madr=%08x\n",
-    //     BCR_SIZE(mdec_in),
-    //     BCR_BCNT(mdec_in),
-    //     CHCR_STEP(mdec_in),
-    //     dma->mdec_in.madr
-    // );
-
-    size_t size = BCR_SIZE(mdec_in) * BCR_BCNT(mdec_in);
-
-    int step = CHCR_STEP(mdec_in) ? -4 : 4;
-
-    for (int i = 0; i < size; i++) {
-        uint32_t data = psx_bus_read32(dma->bus, dma->mdec_in.madr);
-
-        psx_bus_write32(dma->bus, 0x1f801820, data);
-
-        dma->mdec_in.madr += step;
-    }
-
-    dma->mdec_in_irq_delay = size;
-
-    dma->mdec_in.chcr = 0;
-    dma->mdec_in.bcr = 0;
-}
-
-void psx_dma_do_mdec_out(psx_dma_t* dma) {
-    if (!CHCR_BUSY(mdec_out))
-        return;
-
-    // printf("dma mdec out size=%04x bcnt=%04x step=%u madr=%08x\n",
-    //     BCR_SIZE(mdec_out),
-    //     BCR_BCNT(mdec_out),
-    //     CHCR_STEP(mdec_out),
-    //     dma->mdec_out.madr
-    // );
-
-    // printf("mdec out transfer\n");
-
-    size_t size = BCR_SIZE(mdec_out) * BCR_BCNT(mdec_out);
-
-    for (int i = 0; i < size; i++) {
-        uint32_t data = psx_bus_read32(dma->bus, 0x1f801820);
-
-        psx_bus_write32(dma->bus, dma->mdec_out.madr, data);
-
-        dma->mdec_out.madr += CHCR_STEP(mdec_out) ? -4 : 4;
-    }
-
-    dma->mdec_out_irq_delay = size;
-
-    dma->mdec_out.chcr = 0;
-    dma->mdec_out.bcr = 0;
-}
-
-void psx_dma_do_gpu_linked(psx_dma_t* dma) {
-    uint32_t hdr = psx_bus_read32(dma->bus, dma->gpu.madr);
-    uint32_t size = hdr >> 24;
-    uint32_t addr = dma->gpu.madr;
-
-    while (true) {
-        while (size--) {
-            addr = (addr + (CHCR_STEP(gpu) ? -4 : 4)) & 0x1ffffc;
-
-            // Get command from linked list
-            uint32_t cmd = psx_bus_read32(dma->bus, addr);
-
-            // Write to GP0
-            psx_bus_write32(dma->bus, 0x1f801810, cmd);
-
-            dma->gpu_irq_delay++;
-        }
-
-        addr = hdr & 0xffffff;
-
-        if (addr == 0xffffff)
-            break;
-
-        hdr = psx_bus_read32(dma->bus, addr);
-        size = hdr >> 24;
-    }
-}
-
-void psx_dma_do_gpu_request(psx_dma_t* dma) {
-    if (!CHCR_BUSY(gpu))
-        return;
-
-    uint32_t size = BCR_SIZE(gpu) * BCR_BCNT(gpu);
-
-    if (CHCR_TDIR(gpu)) {
-        for (int i = 0; i < size; i++) {
-            uint32_t data = psx_bus_read32(dma->bus, dma->gpu.madr);
-
-            psx_bus_write32(dma->bus, 0x1f801810, data);
-
-            dma->gpu.madr += CHCR_STEP(gpu) ? -4 : 4;
-        }
-    } else {
-        for (int i = 0; i < size; i++) {
-            uint32_t data = psx_bus_read32(dma->bus, 0x1f801810);
-
-            psx_bus_write32(dma->bus, dma->gpu.madr, data);
-
-            dma->gpu.madr += CHCR_STEP(gpu) ? -4 : 4;
-        }
-    }
-
-    dma->gpu_irq_delay = size;
-}
-
-void psx_dma_do_gpu_burst(psx_dma_t* dma) {
-    printf("GPU DMA burst sync mode unimplemented\n");
-
-    exit(1);
-}
-
-psx_dma_do_fn_t g_psx_dma_gpu_table[] = {
-    psx_dma_do_gpu_burst,
-    psx_dma_do_gpu_request,
-    psx_dma_do_gpu_linked
-};
-
-#define TEST_SET_IRQ_FLAG()
-
-void psx_dma_do_gpu(psx_dma_t* dma) {
-    if (!CHCR_BUSY(gpu))
-        return;
-
-    // log_error("GPU DMA transfer: madr=%08x, dir=%s, sync=%s (%u), step=%s, size=%x",
-    //     dma->gpu.madr,
-    //     CHCR_TDIR(gpu) ? "to device" : "to RAM",
-    //     g_psx_dma_sync_type_name_table[CHCR_SYNC(gpu)], CHCR_SYNC(gpu),
-    //     CHCR_STEP(gpu) ? "decrementing" : "incrementing",
-    //     BCR_SIZE(gpu)
-    // );
-
-    g_psx_dma_gpu_table[CHCR_SYNC(gpu)](dma);
-
-    // Clear BCR and CHCR trigger and busy bits
-    dma->gpu.chcr &= ~(CHCR_BUSY_MASK | CHCR_TRIG_MASK);
-    dma->gpu.bcr = 0;
-}
-
-void psx_dma_do_cdrom(psx_dma_t* dma) {
-    if (!CHCR_BUSY(cdrom))
-        return;
-
-    // printf("CDROM DMA transfer: madr=%08x, dir=%s, sync=%s (%u), step=%s, size=%x\n",
-    //     dma->cdrom.madr,
-    //     CHCR_TDIR(cdrom) ? "to device" : "to RAM",
-    //     g_psx_dma_sync_type_name_table[CHCR_SYNC(cdrom)], CHCR_SYNC(cdrom),
-    //     CHCR_STEP(cdrom) ? "decrementing" : "incrementing",
-    //     BCR_SIZE(cdrom)
-    // );
-
-    // printf("DICR: force=%u, en=%02x, irqen=%u, flags=%02x\n",
-    //     (dma->dicr >> 15) & 1,
-    //     (dma->dicr >> 16) & 0x7f,
-    //     (dma->dicr >> 23) & 1,
-    //     (dma->dicr >> 24) & 0x7f
-    // );
-
-    uint32_t size = BCR_SIZE(cdrom);
-
-    if (!size) {
-        printf("0 sized CDROM DMA\n");
-
-        exit(1);
-    }
-
-    dma->cdrom_irq_delay = size * 24;
-
-    if (!CHCR_TDIR(cdrom)) {
-        for (int i = 0; i < size; i++) {
-            uint32_t data = 0;
-            
-            data |= psx_bus_read8(dma->bus, 0x1f801802) << 0;
-            data |= psx_bus_read8(dma->bus, 0x1f801802) << 8;
-            data |= psx_bus_read8(dma->bus, 0x1f801802) << 16;
-            data |= psx_bus_read8(dma->bus, 0x1f801802) << 24;
-
-            psx_bus_write32(dma->bus, dma->cdrom.madr, data);
-
-            dma->cdrom.madr += CHCR_STEP(cdrom) ? -4 : 4;
-        }
-    } else {
-        log_fatal("Invalid CDROM DMA transfer direction");
-    }
-    
-    // Clear BCR and CHCR trigger and busy bits
-    dma->cdrom.chcr = 0;
-    dma->cdrom.bcr = 0;
-}
-
-void psx_dma_do_spu(psx_dma_t* dma) {
-    if (!CHCR_BUSY(spu))
-        return;
-
-    // log_set_quiet(0);
-    // log_fatal("SPU DMA transfer: madr=%08x, dir=%s, sync=%s (%u), step=%s, size=%x, blocks=%u",
-    //     dma->spu.madr,
-    //     CHCR_TDIR(spu) ? "to device" : "to RAM",
-    //     g_psx_dma_sync_type_name_table[CHCR_SYNC(spu)], CHCR_SYNC(spu),
-    //     CHCR_STEP(spu) ? "decrementing" : "incrementing",
-    //     BCR_SIZE(spu), BCR_BCNT(spu)
-    // );
-
-    // log_fatal("DICR: force=%u, en=%02x, irqen=%u, flags=%02x",
-    //     (dma->dicr >> 15) & 1,
-    //     (dma->dicr >> 16) & 0x7f,
-    //     (dma->dicr >> 23) & 1,
-    //     (dma->dicr >> 24) & 0x7f
-    // );
-    // log_set_quiet(1);
-
-    uint32_t size = BCR_SIZE(spu);
-    uint32_t blocks = BCR_BCNT(spu);
-
-    if (!size) {
-        log_fatal("0 sized SPU DMA");
-
-        // exit(1);
-    }
-
-    dma->spu_irq_delay = BCR_SIZE(spu) * BCR_BCNT(spu);
-
-    if (CHCR_TDIR(spu)) {
-        for (int j = 0; j < blocks; j++) {
-            for (int i = 0; i < size; i++) {
-                uint32_t data = psx_bus_read32(dma->bus, dma->spu.madr);
-
-                psx_bus_write16(dma->bus, 0x1f801da8, data & 0xffff);
-                psx_bus_write16(dma->bus, 0x1f801da8, data >> 16);
-
-                dma->spu.madr += CHCR_STEP(spu) ? -4 : 4;
-            }
-        }
-    } else {
-        for (int j = 0; j < blocks; j++) {
-            for (int i = 0; i < size; i++) {
-                uint32_t data;
-
-                data  = psx_bus_read16(dma->bus, 0x1f801da8);
-                data |= psx_bus_read16(dma->bus, 0x1f801da8) << 16;
-
-                psx_bus_write32(dma->bus, dma->spu.madr, data);
-
-                dma->spu.madr += CHCR_STEP(spu) ? -4 : 4;
-            }
-        }
-
-    }
-    
-    // Clear BCR and CHCR trigger and busy bits
-    dma->spu.chcr = 0;
-    dma->spu.bcr = 0;
-}
-
-void psx_dma_do_pio(psx_dma_t* dma) {
-    log_fatal("PIO DMA channel unimplemented");
-}
-
-void psx_dma_do_otc(psx_dma_t* dma) {
-    if ((!(dma->dpcr & DPCR_DMA6EN)) || (!CHCR_TRIG(otc)) || (!CHCR_BUSY(otc)))
-        return;
-
-    // log_fatal("OTC DMA transfer: madr=%08x, dir=%s, sync=%s, step=%s, size=%x",
-    //     dma->otc.madr,
-    //     CHCR_TDIR(otc) ? "to device" : "to RAM",
-    //     CHCR_SYNC(otc) ? "other" : "burst",
-    //     CHCR_STEP(otc) ? "decrementing" : "incrementing",
-    //     BCR_SIZE(otc)
-    // );
-
-    uint32_t size = BCR_SIZE(otc);
-
-    if (!size)
-        size = 0x10000;
-
-    for (int i = size; i > 0; i--) {
-        uint32_t addr = (i != 1) ? (dma->otc.madr - 4) : 0xffffff;
-
-        psx_bus_write32(dma->bus, dma->otc.madr, addr & 0xffffff);
-
-        dma->otc.madr -= 4;
-    }
-
-    dma->otc_irq_delay = size;
-
-    // Clear BCR and CHCR trigger and busy bits
-    dma->otc.chcr = 0;
-    //dma->otc.chcr &= ~(CHCR_BUSY_MASK | CHCR_TRIG_MASK);
-    dma->otc.bcr = 0;
-}
-
-void psx_dma_update(psx_dma_t* dma, int cyc) {
-    if (dma->cdrom_irq_delay) {
-        dma->cdrom_irq_delay = 0;
-
-        if ((dma->dicr & DICR_DMA3EN) && !dma->cdrom_irq_delay)
-            dma->dicr |= DICR_DMA3FL;
-    }
-
-    if (dma->spu_irq_delay) {
-        dma->spu_irq_delay -= cyc;
-
-        if (dma->spu_irq_delay <= 0)
-            if (dma->dicr & DICR_DMA4EN)
-                dma->dicr |= DICR_DMA4FL;
-    }
-
-    if (dma->gpu_irq_delay) {
-        dma->gpu_irq_delay = 0;
-
-        if (!dma->gpu_irq_delay)
-            if (dma->dicr & DICR_DMA2EN)
-                dma->dicr |= DICR_DMA2FL;
-    }
-
-    if (dma->otc_irq_delay) {
-        dma->otc_irq_delay = 0;
-
-        if (!dma->otc_irq_delay)
-            if (dma->dicr & DICR_DMA6EN)
-                dma->dicr |= DICR_DMA6FL;
-    }
-
-    if (dma->mdec_in_irq_delay) {
-        dma->mdec_in_irq_delay = 0;
-
-        if (!dma->mdec_in_irq_delay)
-            if (dma->dicr & DICR_DMA0EN)
-                dma->dicr |= DICR_DMA0FL;
-    }
-
-    if (dma->mdec_out_irq_delay) {
-        dma->mdec_out_irq_delay = 0;
-
-        if (!dma->mdec_out_irq_delay)
-            if (dma->dicr & DICR_DMA1EN)
-                dma->dicr |= DICR_DMA1FL;
-    }
-
-    int prev_irq_signal = (dma->dicr & DICR_IRQSI) != 0;
-    int irq_on_flags = (dma->dicr & DICR_IRQEN) != 0;
-    int force_irq = (dma->dicr & DICR_FORCE) != 0;
-    int irq = (dma->dicr & DICR_FLAGS) != 0;
-
-    int irq_signal = force_irq || ((irq & irq_on_flags) != 0);
-
-    if (irq_signal && !prev_irq_signal)
-        psx_ic_irq(dma->ic, IC_DMA);
-    
-    dma->dicr &= ~DICR_IRQSI;
-    dma->dicr |= irq_signal << 31;
-}
-
-void psx_dma_destroy(psx_dma_t* dma) {
-    free(dma);
-}
-
+#include "dma.h"
+#include "../log.h"
+
+#include <stdint.h>
+#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));
+}
+
+const uint32_t g_psx_dma_ctrl_hw_1_table[] = {
+    0x00000000, 0x00000000, 0x00000000,
+    0x00000000, 0x00000000, 0x00000000,
+    0x00000002
+};
+
+const uint32_t g_psx_dma_ctrl_hw_0_table[] = {
+    0x71770703, 0x71770703, 0x71770703,
+    0x71770703, 0x71770703, 0x71770703,
+    0x50000002
+};
+
+const psx_dma_do_fn_t g_psx_dma_do_table[] = {
+    psx_dma_do_mdec_in,
+    psx_dma_do_mdec_out,
+    psx_dma_do_gpu,
+    psx_dma_do_cdrom,
+    psx_dma_do_spu,
+    psx_dma_do_pio,
+    psx_dma_do_otc
+};
+
+#define CR(c, r) *((&dma->mdec_in.madr) + (c * 3) + r)
+
+void psx_dma_init(psx_dma_t* dma, psx_bus_t* bus, psx_ic_t* ic) {
+    memset(dma, 0, sizeof(psx_dma_t));
+
+    dma->io_base = PSX_DMAR_BEGIN;
+    dma->io_size = PSX_DMAR_SIZE;
+
+    dma->bus = bus;
+    dma->ic = ic;
+
+    dma->dpcr = 0x07654321;
+}
+
+uint32_t psx_dma_read32(psx_dma_t* dma, uint32_t offset) {
+    if (offset < 0x70) {
+        int channel = (offset >> 4) & 0x7;
+        int reg = (offset >> 2) & 0x3;
+        uint32_t cr = CR(channel, reg);
+
+        if (reg == 2) {
+            cr |= g_psx_dma_ctrl_hw_1_table[channel];
+            cr &= g_psx_dma_ctrl_hw_0_table[channel];
+        }
+
+        log_error("DMA channel %u register %u (%08x) read %08x", channel, reg, PSX_DMAR_BEGIN + offset, cr);
+        
+        return cr;
+    } else {
+        switch (offset) {
+            case 0x70: log_error("DMA control read %08x", dma->dpcr); return dma->dpcr;
+            case 0x74: log_error("DMA irqc    read %08x", dma->dicr); return dma->dicr;
+
+            default: {
+                log_error("Unhandled 32-bit DMA read at offset %08x", offset);
+
+                return 0x0;
+            }
+        }
+    }
+}
+
+uint16_t psx_dma_read16(psx_dma_t* dma, uint32_t offset) {
+    switch (offset) {
+        case 0x70: return dma->dpcr;
+        case 0x74: return dma->dicr & 0xffff;
+        case 0x76: return dma->dicr >> 16;        
+
+        default: {
+            printf("Unhandled 16-bit DMA read at offset %08x\n", offset);
+
+            return 0x0;
+        }
+    }
+}
+
+uint8_t psx_dma_read8(psx_dma_t* dma, uint32_t offset) {
+    switch (offset) {
+        case 0x70: return dma->dpcr;
+        case 0x74: return (dma->dicr >> 0) & 0xff;
+        case 0x75: return (dma->dicr >> 8) & 0xff;
+        case 0x76: return (dma->dicr >> 16) & 0xff;
+        case 0x77: return (dma->dicr >> 24) & 0xff;
+
+        default: {
+            printf("Unhandled 8-bit DMA read at offset %08x\n", offset);
+
+            return 0x0;
+        }
+    }
+}
+
+void dma_write_dicr(psx_dma_t* dma, uint32_t value) {
+    uint32_t ack = value & DICR_FLAGS;
+    uint32_t flags = dma->dicr & DICR_FLAGS;
+
+    flags &= (~ack);
+    flags &= DICR_FLAGS;
+
+    dma->dicr &= 0x80000000;
+    dma->dicr |= flags;
+    dma->dicr |= value & 0xffffff;
+}
+
+void psx_dma_write32(psx_dma_t* dma, uint32_t offset, uint32_t value) {
+    if (offset < 0x70) {
+        int channel = (offset >> 4) & 0x7;
+        int reg = (offset >> 2) & 0x3;
+
+        CR(channel, reg) = value;
+
+        log_error("DMA channel %u register %u write (%08x) %08x", channel, reg, PSX_DMAR_BEGIN + offset, value);
+
+        if (reg == 2)
+            g_psx_dma_do_table[channel](dma);
+    } else {
+        switch (offset) {
+            case 0x70: log_error("DMA control write %08x", value); dma->dpcr = value; break;
+            case 0x74: {
+                dma_write_dicr(dma, value);
+            } break;
+
+            default: {
+                log_error("Unhandled 32-bit DMA write at offset %08x (%08x)", offset, value);
+            } break;
+        }
+    }
+}
+
+void psx_dma_write16(psx_dma_t* dma, uint32_t offset, uint16_t value) {
+    switch (offset) {
+        case 0x74: dma_write_dicr(dma, ((uint32_t)value) << 0);
+        case 0x76: dma_write_dicr(dma, ((uint32_t)value) << 16);
+        default: {
+            log_fatal("Unhandled 16-bit DMA write at offset %08x (%04x)", offset, value);
+
+            //exit(1);
+        } break;
+    }
+}
+
+void psx_dma_write8(psx_dma_t* dma, uint32_t offset, uint8_t value) {
+    switch (offset) {
+        // DICR 8-bit???
+        case 0x74: dma_write_dicr(dma, ((uint32_t)value) << 0); break;
+        case 0x75: dma_write_dicr(dma, ((uint32_t)value) << 8); break;
+        case 0x76: dma_write_dicr(dma, ((uint32_t)value) << 16); break;
+        case 0x77: dma_write_dicr(dma, ((uint32_t)value) << 24); break;
+        default: {
+            log_fatal("Unhandled 8-bit DMA write at offset %08x (%02x)", offset, value);
+
+            //exit(1);
+        } break;
+    }
+}
+
+const char* g_psx_dma_sync_type_name_table[] = {
+    "burst",
+    "request",
+    "linked",
+    "reserved"
+};
+
+void psx_dma_do_mdec_in(psx_dma_t* dma) {
+    if (!CHCR_BUSY(mdec_in))
+        return;
+
+    // printf("dma mdec in size=%04x bcnt=%04x step=%u madr=%08x\n",
+    //     BCR_SIZE(mdec_in),
+    //     BCR_BCNT(mdec_in),
+    //     CHCR_STEP(mdec_in),
+    //     dma->mdec_in.madr
+    // );
+
+    size_t size = BCR_SIZE(mdec_in) * BCR_BCNT(mdec_in);
+
+    int step = CHCR_STEP(mdec_in) ? -4 : 4;
+
+    for (int i = 0; i < size; i++) {
+        uint32_t data = psx_bus_read32(dma->bus, dma->mdec_in.madr);
+
+        psx_bus_write32(dma->bus, 0x1f801820, data);
+
+        dma->mdec_in.madr += step;
+    }
+
+    dma->mdec_in_irq_delay = size;
+
+    dma->mdec_in.chcr = 0;
+    dma->mdec_in.bcr = 0;
+}
+
+void psx_dma_do_mdec_out(psx_dma_t* dma) {
+    if (!CHCR_BUSY(mdec_out))
+        return;
+
+    // printf("dma mdec out size=%04x bcnt=%04x step=%u madr=%08x\n",
+    //     BCR_SIZE(mdec_out),
+    //     BCR_BCNT(mdec_out),
+    //     CHCR_STEP(mdec_out),
+    //     dma->mdec_out.madr
+    // );
+
+    // printf("mdec out transfer\n");
+
+    size_t size = BCR_SIZE(mdec_out) * BCR_BCNT(mdec_out);
+
+    for (int i = 0; i < size; i++) {
+        uint32_t data = psx_bus_read32(dma->bus, 0x1f801820);
+
+        psx_bus_write32(dma->bus, dma->mdec_out.madr, data);
+
+        dma->mdec_out.madr += CHCR_STEP(mdec_out) ? -4 : 4;
+    }
+
+    dma->mdec_out_irq_delay = size;
+
+    dma->mdec_out.chcr = 0;
+    dma->mdec_out.bcr = 0;
+}
+
+void psx_dma_do_gpu_linked(psx_dma_t* dma) {
+    uint32_t hdr = psx_bus_read32(dma->bus, dma->gpu.madr);
+    uint32_t size = hdr >> 24;
+    uint32_t addr = dma->gpu.madr;
+
+    while (true) {
+        while (size--) {
+            addr = (addr + (CHCR_STEP(gpu) ? -4 : 4)) & 0x1ffffc;
+
+            // Get command from linked list
+            uint32_t cmd = psx_bus_read32(dma->bus, addr);
+
+            // Write to GP0
+            psx_bus_write32(dma->bus, 0x1f801810, cmd);
+
+            dma->gpu_irq_delay++;
+        }
+
+        addr = hdr & 0xffffff;
+
+        if (addr == 0xffffff)
+            break;
+
+        hdr = psx_bus_read32(dma->bus, addr);
+        size = hdr >> 24;
+    }
+}
+
+void psx_dma_do_gpu_request(psx_dma_t* dma) {
+    if (!CHCR_BUSY(gpu))
+        return;
+
+    uint32_t size = BCR_SIZE(gpu) * BCR_BCNT(gpu);
+
+    if (CHCR_TDIR(gpu)) {
+        for (int i = 0; i < size; i++) {
+            uint32_t data = psx_bus_read32(dma->bus, dma->gpu.madr);
+
+            psx_bus_write32(dma->bus, 0x1f801810, data);
+
+            dma->gpu.madr += CHCR_STEP(gpu) ? -4 : 4;
+        }
+    } else {
+        for (int i = 0; i < size; i++) {
+            uint32_t data = psx_bus_read32(dma->bus, 0x1f801810);
+
+            psx_bus_write32(dma->bus, dma->gpu.madr, data);
+
+            dma->gpu.madr += CHCR_STEP(gpu) ? -4 : 4;
+        }
+    }
+
+    dma->gpu_irq_delay = size;
+}
+
+void psx_dma_do_gpu_burst(psx_dma_t* dma) {
+    printf("GPU DMA burst sync mode unimplemented\n");
+
+    exit(1);
+}
+
+psx_dma_do_fn_t g_psx_dma_gpu_table[] = {
+    psx_dma_do_gpu_burst,
+    psx_dma_do_gpu_request,
+    psx_dma_do_gpu_linked
+};
+
+#define TEST_SET_IRQ_FLAG()
+
+void psx_dma_do_gpu(psx_dma_t* dma) {
+    if (!CHCR_BUSY(gpu))
+        return;
+
+    // log_error("GPU DMA transfer: madr=%08x, dir=%s, sync=%s (%u), step=%s, size=%x",
+    //     dma->gpu.madr,
+    //     CHCR_TDIR(gpu) ? "to device" : "to RAM",
+    //     g_psx_dma_sync_type_name_table[CHCR_SYNC(gpu)], CHCR_SYNC(gpu),
+    //     CHCR_STEP(gpu) ? "decrementing" : "incrementing",
+    //     BCR_SIZE(gpu)
+    // );
+
+    g_psx_dma_gpu_table[CHCR_SYNC(gpu)](dma);
+
+    // Clear BCR and CHCR trigger and busy bits
+    dma->gpu.chcr &= ~(CHCR_BUSY_MASK | CHCR_TRIG_MASK);
+    dma->gpu.bcr = 0;
+}
+
+void psx_dma_do_cdrom(psx_dma_t* dma) {
+    if (!CHCR_BUSY(cdrom))
+        return;
+
+    // printf("CDROM DMA transfer: madr=%08x, dir=%s, sync=%s (%u), step=%s, size=%x\n",
+    //     dma->cdrom.madr,
+    //     CHCR_TDIR(cdrom) ? "to device" : "to RAM",
+    //     g_psx_dma_sync_type_name_table[CHCR_SYNC(cdrom)], CHCR_SYNC(cdrom),
+    //     CHCR_STEP(cdrom) ? "decrementing" : "incrementing",
+    //     BCR_SIZE(cdrom)
+    // );
+
+    // printf("DICR: force=%u, en=%02x, irqen=%u, flags=%02x\n",
+    //     (dma->dicr >> 15) & 1,
+    //     (dma->dicr >> 16) & 0x7f,
+    //     (dma->dicr >> 23) & 1,
+    //     (dma->dicr >> 24) & 0x7f
+    // );
+
+    uint32_t size = BCR_SIZE(cdrom);
+
+    if (!size) {
+        printf("0 sized CDROM DMA\n");
+
+        exit(1);
+    }
+
+    dma->cdrom_irq_delay = size * 24;
+
+    if (!CHCR_TDIR(cdrom)) {
+        for (int i = 0; i < size; i++) {
+            uint32_t data = 0;
+            
+            data |= psx_bus_read8(dma->bus, 0x1f801802) << 0;
+            data |= psx_bus_read8(dma->bus, 0x1f801802) << 8;
+            data |= psx_bus_read8(dma->bus, 0x1f801802) << 16;
+            data |= psx_bus_read8(dma->bus, 0x1f801802) << 24;
+
+            psx_bus_write32(dma->bus, dma->cdrom.madr, data);
+
+            dma->cdrom.madr += CHCR_STEP(cdrom) ? -4 : 4;
+        }
+    } else {
+        log_fatal("Invalid CDROM DMA transfer direction");
+    }
+    
+    // Clear BCR and CHCR trigger and busy bits
+    dma->cdrom.chcr = 0;
+    dma->cdrom.bcr = 0;
+}
+
+void psx_dma_do_spu(psx_dma_t* dma) {
+    if (!CHCR_BUSY(spu))
+        return;
+
+    // log_set_quiet(0);
+    // log_fatal("SPU DMA transfer: madr=%08x, dir=%s, sync=%s (%u), step=%s, size=%x, blocks=%u",
+    //     dma->spu.madr,
+    //     CHCR_TDIR(spu) ? "to device" : "to RAM",
+    //     g_psx_dma_sync_type_name_table[CHCR_SYNC(spu)], CHCR_SYNC(spu),
+    //     CHCR_STEP(spu) ? "decrementing" : "incrementing",
+    //     BCR_SIZE(spu), BCR_BCNT(spu)
+    // );
+
+    // log_fatal("DICR: force=%u, en=%02x, irqen=%u, flags=%02x",
+    //     (dma->dicr >> 15) & 1,
+    //     (dma->dicr >> 16) & 0x7f,
+    //     (dma->dicr >> 23) & 1,
+    //     (dma->dicr >> 24) & 0x7f
+    // );
+    // log_set_quiet(1);
+
+    uint32_t size = BCR_SIZE(spu);
+    uint32_t blocks = BCR_BCNT(spu);
+
+    if (!size) {
+        log_fatal("0 sized SPU DMA");
+
+        // exit(1);
+    }
+
+    dma->spu_irq_delay = BCR_SIZE(spu) * BCR_BCNT(spu);
+
+    if (CHCR_TDIR(spu)) {
+        for (int j = 0; j < blocks; j++) {
+            for (int i = 0; i < size; i++) {
+                uint32_t data = psx_bus_read32(dma->bus, dma->spu.madr);
+
+                psx_bus_write16(dma->bus, 0x1f801da8, data & 0xffff);
+                psx_bus_write16(dma->bus, 0x1f801da8, data >> 16);
+
+                dma->spu.madr += CHCR_STEP(spu) ? -4 : 4;
+            }
+        }
+    } else {
+        for (int j = 0; j < blocks; j++) {
+            for (int i = 0; i < size; i++) {
+                uint32_t data;
+
+                data  = psx_bus_read16(dma->bus, 0x1f801da8);
+                data |= psx_bus_read16(dma->bus, 0x1f801da8) << 16;
+
+                psx_bus_write32(dma->bus, dma->spu.madr, data);
+
+                dma->spu.madr += CHCR_STEP(spu) ? -4 : 4;
+            }
+        }
+
+    }
+    
+    // Clear BCR and CHCR trigger and busy bits
+    dma->spu.chcr = 0;
+    dma->spu.bcr = 0;
+}
+
+void psx_dma_do_pio(psx_dma_t* dma) {
+    log_fatal("PIO DMA channel unimplemented");
+}
+
+void psx_dma_do_otc(psx_dma_t* dma) {
+    if ((!(dma->dpcr & DPCR_DMA6EN)) || (!CHCR_TRIG(otc)) || (!CHCR_BUSY(otc)))
+        return;
+
+    // log_fatal("OTC DMA transfer: madr=%08x, dir=%s, sync=%s, step=%s, size=%x",
+    //     dma->otc.madr,
+    //     CHCR_TDIR(otc) ? "to device" : "to RAM",
+    //     CHCR_SYNC(otc) ? "other" : "burst",
+    //     CHCR_STEP(otc) ? "decrementing" : "incrementing",
+    //     BCR_SIZE(otc)
+    // );
+
+    uint32_t size = BCR_SIZE(otc);
+
+    if (!size)
+        size = 0x10000;
+
+    for (int i = size; i > 0; i--) {
+        uint32_t addr = (i != 1) ? (dma->otc.madr - 4) : 0xffffff;
+
+        psx_bus_write32(dma->bus, dma->otc.madr, addr & 0xffffff);
+
+        dma->otc.madr -= 4;
+    }
+
+    dma->otc_irq_delay = size;
+
+    // Clear BCR and CHCR trigger and busy bits
+    dma->otc.chcr = 0;
+    //dma->otc.chcr &= ~(CHCR_BUSY_MASK | CHCR_TRIG_MASK);
+    dma->otc.bcr = 0;
+}
+
+void psx_dma_update(psx_dma_t* dma, int cyc) {
+    if (dma->cdrom_irq_delay) {
+        dma->cdrom_irq_delay = 0;
+
+        if ((dma->dicr & DICR_DMA3EN) && !dma->cdrom_irq_delay)
+            dma->dicr |= DICR_DMA3FL;
+    }
+
+    if (dma->spu_irq_delay) {
+        dma->spu_irq_delay -= cyc;
+
+        if (dma->spu_irq_delay <= 0)
+            if (dma->dicr & DICR_DMA4EN)
+                dma->dicr |= DICR_DMA4FL;
+    }
+
+    if (dma->gpu_irq_delay) {
+        dma->gpu_irq_delay = 0;
+
+        if (!dma->gpu_irq_delay)
+            if (dma->dicr & DICR_DMA2EN)
+                dma->dicr |= DICR_DMA2FL;
+    }
+
+    if (dma->otc_irq_delay) {
+        dma->otc_irq_delay = 0;
+
+        if (!dma->otc_irq_delay)
+            if (dma->dicr & DICR_DMA6EN)
+                dma->dicr |= DICR_DMA6FL;
+    }
+
+    if (dma->mdec_in_irq_delay) {
+        dma->mdec_in_irq_delay = 0;
+
+        if (!dma->mdec_in_irq_delay)
+            if (dma->dicr & DICR_DMA0EN)
+                dma->dicr |= DICR_DMA0FL;
+    }
+
+    if (dma->mdec_out_irq_delay) {
+        dma->mdec_out_irq_delay = 0;
+
+        if (!dma->mdec_out_irq_delay)
+            if (dma->dicr & DICR_DMA1EN)
+                dma->dicr |= DICR_DMA1FL;
+    }
+
+    int prev_irq_signal = (dma->dicr & DICR_IRQSI) != 0;
+    int irq_on_flags = (dma->dicr & DICR_IRQEN) != 0;
+    int force_irq = (dma->dicr & DICR_FORCE) != 0;
+    int irq = (dma->dicr & DICR_FLAGS) != 0;
+
+    int irq_signal = force_irq || ((irq & irq_on_flags) != 0);
+
+    if (irq_signal && !prev_irq_signal)
+        psx_ic_irq(dma->ic, IC_DMA);
+    
+    dma->dicr &= ~DICR_IRQSI;
+    dma->dicr |= irq_signal << 31;
+}
+
+void psx_dma_destroy(psx_dma_t* dma) {
+    free(dma);
+}
+
 #undef CR
\ No newline at end of file
--- a/psx/dev/dma.h
+++ b/psx/dev/dma.h
@@ -1,171 +1,171 @@
-#ifndef DMA_H
-#define DMA_H
-
-#include <stdint.h>
-
-#define PSX_DMAR_BEGIN 0x1f801080
-#define PSX_DMAR_SIZE  0x80
-#define PSX_DMAR_END   0x1f8010ff
-
-#include "../bus.h"
-#include "ic.h"
-
-typedef struct {
-    uint32_t madr;
-    uint32_t bcr;
-    uint32_t chcr;
-} dma_channel_t;
-
-typedef struct {
-    uint32_t bus_delay;
-    uint32_t io_base, io_size;
-
-    psx_bus_t* bus;
-    psx_ic_t* ic;
-
-    dma_channel_t mdec_in;
-    dma_channel_t mdec_out;
-    dma_channel_t gpu;
-    dma_channel_t cdrom;
-    dma_channel_t spu;
-    dma_channel_t pio;
-    dma_channel_t otc;
-
-    int mdec_in_irq_delay;
-    int mdec_out_irq_delay;
-    int cdrom_irq_delay;
-    int spu_irq_delay;
-    int gpu_irq_delay;
-    int otc_irq_delay;
-
-    uint32_t dpcr;
-    uint32_t dicr;
-} psx_dma_t;
-
-psx_dma_t* psx_dma_create();
-void psx_dma_init(psx_dma_t*, psx_bus_t*, psx_ic_t*);
-void psx_dma_do_mdec_in(psx_dma_t*);
-void psx_dma_do_mdec_out(psx_dma_t*);
-void psx_dma_do_gpu(psx_dma_t*);
-void psx_dma_do_cdrom(psx_dma_t*);
-void psx_dma_do_spu(psx_dma_t*);
-void psx_dma_do_pio(psx_dma_t*);
-void psx_dma_do_otc(psx_dma_t*);
-void psx_dma_perform(psx_dma_t*, int);
-uint32_t psx_dma_read32(psx_dma_t*, uint32_t);
-uint16_t psx_dma_read16(psx_dma_t*, uint32_t);
-uint8_t psx_dma_read8(psx_dma_t*, uint32_t);
-void psx_dma_write32(psx_dma_t*, uint32_t, uint32_t);
-void psx_dma_write16(psx_dma_t*, uint32_t, uint16_t);
-void psx_dma_write8(psx_dma_t*, uint32_t, uint8_t);
-void psx_dma_destroy(psx_dma_t*);
-void psx_dma_update(psx_dma_t*, int);
-
-typedef void (*psx_dma_do_fn_t)(psx_dma_t*);
-
-/*
-  0-2   DMA0, MDECin  Priority      (0..7; 0=Highest, 7=Lowest)
-  3     DMA0, MDECin  Master Enable (0=Disable, 1=Enable)
-  4-6   DMA1, MDECout Priority      (0..7; 0=Highest, 7=Lowest)
-  7     DMA1, MDECout Master Enable (0=Disable, 1=Enable)
-  8-10  DMA2, GPU     Priority      (0..7; 0=Highest, 7=Lowest)
-  11    DMA2, GPU     Master Enable (0=Disable, 1=Enable)
-  12-14 DMA3, CDROM   Priority      (0..7; 0=Highest, 7=Lowest)
-  15    DMA3, CDROM   Master Enable (0=Disable, 1=Enable)
-  16-18 DMA4, SPU     Priority      (0..7; 0=Highest, 7=Lowest)
-  19    DMA4, SPU     Master Enable (0=Disable, 1=Enable)
-  20-22 DMA5, PIO     Priority      (0..7; 0=Highest, 7=Lowest)
-  23    DMA5, PIO     Master Enable (0=Disable, 1=Enable)
-  24-26 DMA6, OTC     Priority      (0..7; 0=Highest, 7=Lowest)
-  27    DMA6, OTC     Master Enable (0=Disable, 1=Enable)
-  28-30 Unknown, Priority Offset or so? (R/W)
-  31    Unknown, no effect? (R/W)
-*/
-
-#define DPCR_DMA0EN 0x00000008
-#define DPCR_DMA1EN 0x00000080
-#define DPCR_DMA2EN 0x00000800
-#define DPCR_DMA3EN 0x00008000
-#define DPCR_DMA4EN 0x00080000
-#define DPCR_DMA5EN 0x00800000
-#define DPCR_DMA6EN 0x08000000
-
-/*
-  0       Transfer Direction    (0=To Main RAM, 1=From Main RAM)
-  1       Memory Address Step   (0=Forward;+4, 1=Backward;-4)
-  2-7     Not used              (always zero)
-  8       Chopping Enable       (0=Normal, 1=Chopping; run CPU during DMA gaps)
-  9-10    SyncMode, Transfer Synchronisation/Mode (0-3):
-            0  Start immediately and transfer all at once (used for CDROM, OTC)
-            1  Sync blocks to DMA requests   (used for MDEC, SPU, and GPU-data)
-            2  Linked-List mode              (used for GPU-command-lists)
-            3  Reserved                      (not used)
-  11-15   Not used              (always zero)
-  16-18   Chopping DMA Window Size (1 SHL N words)
-  19      Not used              (always zero)
-  20-22   Chopping CPU Window Size (1 SHL N clks)
-  23      Not used              (always zero)
-  24      Start/Busy            (0=Stopped/Completed, 1=Start/Enable/Busy)
-  25-27   Not used              (always zero)
-  28      Start/Trigger         (0=Normal, 1=Manual Start; use for SyncMode=0)
-  29      Unknown (R/W) Pause?  (0=No, 1=Pause?)     (For SyncMode=0 only?)
-  30      Unknown (R/W)
-  31      Not used              (always zero)
-*/
-
-#define CHCR_TDIR_MASK 0x00000001
-#define CHCR_STEP_MASK 0x00000002
-#define CHCR_CPEN_MASK 0x00000100
-#define CHCR_SYNC_MASK 0x00000600
-#define CHCR_CDWS_MASK 0x00070000
-#define CHCR_CCWS_MASK 0x00380000
-#define CHCR_BUSY_MASK 0x01000000
-#define CHCR_TRIG_MASK 0x10000000
-
-#define SYNC_SHIF 9
-#define CDWS_SHIF 16
-#define CCWS_SHIF 19
-
-#define CHCR_TDIR(c) (dma->c.chcr & CHCR_TDIR_MASK)
-#define CHCR_STEP(c) (dma->c.chcr & CHCR_STEP_MASK)
-#define CHCR_CPEN(c) (dma->c.chcr & CHCR_CPEN_MASK)
-#define CHCR_SYNC(c) ((dma->c.chcr & CHCR_SYNC_MASK) >> SYNC_SHIF)
-#define CHCR_CDWS(c) ((dma->c.chcr & CHCR_CDWS_MASK) >> CDWS_SHIF)
-#define CHCR_CCWS(c) ((dma->c.chcr & CHCR_CCWS_MASK) >> CCWS_SHIF)
-#define CHCR_BUSY(c) (dma->c.chcr & CHCR_BUSY_MASK)
-#define CHCR_TRIG(c) (dma->c.chcr & CHCR_TRIG_MASK)
-
-#define BCR_SIZE(c) (dma->c.bcr & 0xffff)
-#define BCR_BCNT(c) ((dma->c.bcr >> 16) & 0xffff)
-
-/*
-  0-5   Unknown  (read/write-able)
-  6-14  Not used (always zero)
-  15    Force IRQ (sets bit31)                        (0=None, 1=Force Bit31=1)
-  16-22 IRQ Enable setting bit24-30 upon DMA0..DMA6    (0=None, 1=Enable)
-  23    IRQ Enable setting bit31 when bit24-30=nonzero (0=None, 1=Enable)
-  24-30 IRQ Flags for DMA0..DMA6    (Write 1 to reset) (0=None, 1=IRQ)
-  31    IRQ Signal (0-to-1 triggers 1F801070h.bit3)    (0=None, 1=IRQ) (R)
-*/
-
-#define DICR_FORCE 0x00008000
-#define DICR_FLGEN 0x007f0000
-#define DICR_IRQEN 0x00800000
-#define DICR_FLAGS 0x7f000000
-#define DICR_IRQSI 0x80000000
-#define DICR_DMA0EN 0x00010000
-#define DICR_DMA1EN 0x00020000
-#define DICR_DMA2EN 0x00040000
-#define DICR_DMA3EN 0x00080000
-#define DICR_DMA4EN 0x00100000
-#define DICR_DMA5EN 0x00200000
-#define DICR_DMA6EN 0x00400000
-#define DICR_DMA0FL 0x01000000
-#define DICR_DMA1FL 0x02000000
-#define DICR_DMA2FL 0x04000000
-#define DICR_DMA3FL 0x08000000
-#define DICR_DMA4FL 0x10000000
-#define DICR_DMA5FL 0x20000000
-#define DICR_DMA6FL 0x40000000
-
+#ifndef DMA_H
+#define DMA_H
+
+#include <stdint.h>
+
+#define PSX_DMAR_BEGIN 0x1f801080
+#define PSX_DMAR_SIZE  0x80
+#define PSX_DMAR_END   0x1f8010ff
+
+#include "../bus.h"
+#include "ic.h"
+
+typedef struct {
+    uint32_t madr;
+    uint32_t bcr;
+    uint32_t chcr;
+} dma_channel_t;
+
+typedef struct {
+    uint32_t bus_delay;
+    uint32_t io_base, io_size;
+
+    psx_bus_t* bus;
+    psx_ic_t* ic;
+
+    dma_channel_t mdec_in;
+    dma_channel_t mdec_out;
+    dma_channel_t gpu;
+    dma_channel_t cdrom;
+    dma_channel_t spu;
+    dma_channel_t pio;
+    dma_channel_t otc;
+
+    int mdec_in_irq_delay;
+    int mdec_out_irq_delay;
+    int cdrom_irq_delay;
+    int spu_irq_delay;
+    int gpu_irq_delay;
+    int otc_irq_delay;
+
+    uint32_t dpcr;
+    uint32_t dicr;
+} psx_dma_t;
+
+psx_dma_t* psx_dma_create();
+void psx_dma_init(psx_dma_t*, psx_bus_t*, psx_ic_t*);
+void psx_dma_do_mdec_in(psx_dma_t*);
+void psx_dma_do_mdec_out(psx_dma_t*);
+void psx_dma_do_gpu(psx_dma_t*);
+void psx_dma_do_cdrom(psx_dma_t*);
+void psx_dma_do_spu(psx_dma_t*);
+void psx_dma_do_pio(psx_dma_t*);
+void psx_dma_do_otc(psx_dma_t*);
+void psx_dma_perform(psx_dma_t*, int);
+uint32_t psx_dma_read32(psx_dma_t*, uint32_t);
+uint16_t psx_dma_read16(psx_dma_t*, uint32_t);
+uint8_t psx_dma_read8(psx_dma_t*, uint32_t);
+void psx_dma_write32(psx_dma_t*, uint32_t, uint32_t);
+void psx_dma_write16(psx_dma_t*, uint32_t, uint16_t);
+void psx_dma_write8(psx_dma_t*, uint32_t, uint8_t);
+void psx_dma_destroy(psx_dma_t*);
+void psx_dma_update(psx_dma_t*, int);
+
+typedef void (*psx_dma_do_fn_t)(psx_dma_t*);
+
+/*
+  0-2   DMA0, MDECin  Priority      (0..7; 0=Highest, 7=Lowest)
+  3     DMA0, MDECin  Master Enable (0=Disable, 1=Enable)
+  4-6   DMA1, MDECout Priority      (0..7; 0=Highest, 7=Lowest)
+  7     DMA1, MDECout Master Enable (0=Disable, 1=Enable)
+  8-10  DMA2, GPU     Priority      (0..7; 0=Highest, 7=Lowest)
+  11    DMA2, GPU     Master Enable (0=Disable, 1=Enable)
+  12-14 DMA3, CDROM   Priority      (0..7; 0=Highest, 7=Lowest)
+  15    DMA3, CDROM   Master Enable (0=Disable, 1=Enable)
+  16-18 DMA4, SPU     Priority      (0..7; 0=Highest, 7=Lowest)
+  19    DMA4, SPU     Master Enable (0=Disable, 1=Enable)
+  20-22 DMA5, PIO     Priority      (0..7; 0=Highest, 7=Lowest)
+  23    DMA5, PIO     Master Enable (0=Disable, 1=Enable)
+  24-26 DMA6, OTC     Priority      (0..7; 0=Highest, 7=Lowest)
+  27    DMA6, OTC     Master Enable (0=Disable, 1=Enable)
+  28-30 Unknown, Priority Offset or so? (R/W)
+  31    Unknown, no effect? (R/W)
+*/
+
+#define DPCR_DMA0EN 0x00000008
+#define DPCR_DMA1EN 0x00000080
+#define DPCR_DMA2EN 0x00000800
+#define DPCR_DMA3EN 0x00008000
+#define DPCR_DMA4EN 0x00080000
+#define DPCR_DMA5EN 0x00800000
+#define DPCR_DMA6EN 0x08000000
+
+/*
+  0       Transfer Direction    (0=To Main RAM, 1=From Main RAM)
+  1       Memory Address Step   (0=Forward;+4, 1=Backward;-4)
+  2-7     Not used              (always zero)
+  8       Chopping Enable       (0=Normal, 1=Chopping; run CPU during DMA gaps)
+  9-10    SyncMode, Transfer Synchronisation/Mode (0-3):
+            0  Start immediately and transfer all at once (used for CDROM, OTC)
+            1  Sync blocks to DMA requests   (used for MDEC, SPU, and GPU-data)
+            2  Linked-List mode              (used for GPU-command-lists)
+            3  Reserved                      (not used)
+  11-15   Not used              (always zero)
+  16-18   Chopping DMA Window Size (1 SHL N words)
+  19      Not used              (always zero)
+  20-22   Chopping CPU Window Size (1 SHL N clks)
+  23      Not used              (always zero)
+  24      Start/Busy            (0=Stopped/Completed, 1=Start/Enable/Busy)
+  25-27   Not used              (always zero)
+  28      Start/Trigger         (0=Normal, 1=Manual Start; use for SyncMode=0)
+  29      Unknown (R/W) Pause?  (0=No, 1=Pause?)     (For SyncMode=0 only?)
+  30      Unknown (R/W)
+  31      Not used              (always zero)
+*/
+
+#define CHCR_TDIR_MASK 0x00000001
+#define CHCR_STEP_MASK 0x00000002
+#define CHCR_CPEN_MASK 0x00000100
+#define CHCR_SYNC_MASK 0x00000600
+#define CHCR_CDWS_MASK 0x00070000
+#define CHCR_CCWS_MASK 0x00380000
+#define CHCR_BUSY_MASK 0x01000000
+#define CHCR_TRIG_MASK 0x10000000
+
+#define SYNC_SHIF 9
+#define CDWS_SHIF 16
+#define CCWS_SHIF 19
+
+#define CHCR_TDIR(c) (dma->c.chcr & CHCR_TDIR_MASK)
+#define CHCR_STEP(c) (dma->c.chcr & CHCR_STEP_MASK)
+#define CHCR_CPEN(c) (dma->c.chcr & CHCR_CPEN_MASK)
+#define CHCR_SYNC(c) ((dma->c.chcr & CHCR_SYNC_MASK) >> SYNC_SHIF)
+#define CHCR_CDWS(c) ((dma->c.chcr & CHCR_CDWS_MASK) >> CDWS_SHIF)
+#define CHCR_CCWS(c) ((dma->c.chcr & CHCR_CCWS_MASK) >> CCWS_SHIF)
+#define CHCR_BUSY(c) (dma->c.chcr & CHCR_BUSY_MASK)
+#define CHCR_TRIG(c) (dma->c.chcr & CHCR_TRIG_MASK)
+
+#define BCR_SIZE(c) (dma->c.bcr & 0xffff)
+#define BCR_BCNT(c) ((dma->c.bcr >> 16) & 0xffff)
+
+/*
+  0-5   Unknown  (read/write-able)
+  6-14  Not used (always zero)
+  15    Force IRQ (sets bit31)                        (0=None, 1=Force Bit31=1)
+  16-22 IRQ Enable setting bit24-30 upon DMA0..DMA6    (0=None, 1=Enable)
+  23    IRQ Enable setting bit31 when bit24-30=nonzero (0=None, 1=Enable)
+  24-30 IRQ Flags for DMA0..DMA6    (Write 1 to reset) (0=None, 1=IRQ)
+  31    IRQ Signal (0-to-1 triggers 1F801070h.bit3)    (0=None, 1=IRQ) (R)
+*/
+
+#define DICR_FORCE 0x00008000
+#define DICR_FLGEN 0x007f0000
+#define DICR_IRQEN 0x00800000
+#define DICR_FLAGS 0x7f000000
+#define DICR_IRQSI 0x80000000
+#define DICR_DMA0EN 0x00010000
+#define DICR_DMA1EN 0x00020000
+#define DICR_DMA2EN 0x00040000
+#define DICR_DMA3EN 0x00080000
+#define DICR_DMA4EN 0x00100000
+#define DICR_DMA5EN 0x00200000
+#define DICR_DMA6EN 0x00400000
+#define DICR_DMA0FL 0x01000000
+#define DICR_DMA1FL 0x02000000
+#define DICR_DMA2FL 0x04000000
+#define DICR_DMA3FL 0x08000000
+#define DICR_DMA4FL 0x10000000
+#define DICR_DMA5FL 0x20000000
+#define DICR_DMA6FL 0x40000000
+
 #endif
\ No newline at end of file
--- a/psx/dev/exp1.c
+++ b/psx/dev/exp1.c
@@ -1,68 +1,68 @@
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "../log.h"
-#include "exp1.h"
-
-psx_exp1_t* psx_exp1_create() {
-    return (psx_exp1_t*)malloc(sizeof(psx_exp1_t));
-}
-
-void psx_exp1_init(psx_exp1_t* exp1, psx_mc1_t* mc1, const char* path) {
-    memset(exp1, 0, sizeof(psx_exp1_t));
-
-    exp1->io_base = PSX_EXP1_BEGIN;
-    exp1->io_size = PSX_EXP1_SIZE;
-
-    exp1->mc1 = mc1;
-    exp1->rom = (uint8_t*)malloc(PSX_EXP1_SIZE);
-
-    memset(exp1->rom, 0xff, PSX_EXP1_SIZE);
-
-    if (path)
-        psx_exp1_load(exp1, path);
-}
-
-void psx_exp1_load(psx_exp1_t* exp1, const char* path) {
-    FILE* file = fopen(path, "rb");
-
-    if (!file) {
-        perror("Error opening expansion ROM file \'%s\'");
-
-        exit(1);
-    }
-
-    fread(exp1->rom, 1, PSX_EXP1_SIZE, file);
-
-    fclose(file);
-}
-
-uint32_t psx_exp1_read32(psx_exp1_t* exp1, uint32_t offset) {
-    return *((uint32_t*)(exp1->rom + offset));
-}
-
-uint16_t psx_exp1_read16(psx_exp1_t* exp1, uint32_t offset) {
-    return *((uint16_t*)(exp1->rom + offset));
-}
-
-uint8_t psx_exp1_read8(psx_exp1_t* exp1, uint32_t offset) {
-    return exp1->rom[offset];
-}
-
-void psx_exp1_write32(psx_exp1_t* exp1, uint32_t offset, uint32_t value) {
-    log_warn("Unhandled 32-bit EXP1 write at offset %08x (%08x)", offset, value);
-}
-
-void psx_exp1_write16(psx_exp1_t* exp1, uint32_t offset, uint16_t value) {
-    log_warn("Unhandled 16-bit EXP1 write at offset %08x (%04x)", offset, value);
-}
-
-void psx_exp1_write8(psx_exp1_t* exp1, uint32_t offset, uint8_t value) {
-    log_warn("Unhandled 8-bit EXP1 write at offset %08x (%02x)", offset, value);
-}
-
-void psx_exp1_destroy(psx_exp1_t* exp1) {
-    free(exp1->rom);
-    free(exp1);
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../log.h"
+#include "exp1.h"
+
+psx_exp1_t* psx_exp1_create() {
+    return (psx_exp1_t*)malloc(sizeof(psx_exp1_t));
+}
+
+void psx_exp1_init(psx_exp1_t* exp1, psx_mc1_t* mc1, const char* path) {
+    memset(exp1, 0, sizeof(psx_exp1_t));
+
+    exp1->io_base = PSX_EXP1_BEGIN;
+    exp1->io_size = PSX_EXP1_SIZE;
+
+    exp1->mc1 = mc1;
+    exp1->rom = (uint8_t*)malloc(PSX_EXP1_SIZE);
+
+    memset(exp1->rom, 0xff, PSX_EXP1_SIZE);
+
+    if (path)
+        psx_exp1_load(exp1, path);
+}
+
+void psx_exp1_load(psx_exp1_t* exp1, const char* path) {
+    FILE* file = fopen(path, "rb");
+
+    if (!file) {
+        perror("Error opening expansion ROM file \'%s\'");
+
+        exit(1);
+    }
+
+    fread(exp1->rom, 1, PSX_EXP1_SIZE, file);
+
+    fclose(file);
+}
+
+uint32_t psx_exp1_read32(psx_exp1_t* exp1, uint32_t offset) {
+    return *((uint32_t*)(exp1->rom + offset));
+}
+
+uint16_t psx_exp1_read16(psx_exp1_t* exp1, uint32_t offset) {
+    return *((uint16_t*)(exp1->rom + offset));
+}
+
+uint8_t psx_exp1_read8(psx_exp1_t* exp1, uint32_t offset) {
+    return exp1->rom[offset];
+}
+
+void psx_exp1_write32(psx_exp1_t* exp1, uint32_t offset, uint32_t value) {
+    log_warn("Unhandled 32-bit EXP1 write at offset %08x (%08x)", offset, value);
+}
+
+void psx_exp1_write16(psx_exp1_t* exp1, uint32_t offset, uint16_t value) {
+    log_warn("Unhandled 16-bit EXP1 write at offset %08x (%04x)", offset, value);
+}
+
+void psx_exp1_write8(psx_exp1_t* exp1, uint32_t offset, uint8_t value) {
+    log_warn("Unhandled 8-bit EXP1 write at offset %08x (%02x)", offset, value);
+}
+
+void psx_exp1_destroy(psx_exp1_t* exp1) {
+    free(exp1->rom);
+    free(exp1);
 }
\ No newline at end of file
--- a/psx/dev/exp1.h
+++ b/psx/dev/exp1.h
@@ -1,31 +1,31 @@
-#ifndef EXP1_H
-#define EXP1_H
-
-#include <stdint.h>
-
-#include "mc1.h"
-
-#define PSX_EXP1_BEGIN 0x1f000000
-#define PSX_EXP1_SIZE  0x80000
-#define PSX_EXP1_END   0x1f080000
-
-typedef struct {
-    uint32_t bus_delay;
-    uint32_t io_base, io_size;
-
-    psx_mc1_t* mc1;
-    uint8_t* rom;
-} psx_exp1_t;
-
-psx_exp1_t* psx_exp1_create();
-void psx_exp1_init(psx_exp1_t*, psx_mc1_t*, const char*);
-void psx_exp1_load(psx_exp1_t*, const char*);
-uint32_t psx_exp1_read32(psx_exp1_t*, uint32_t);
-uint16_t psx_exp1_read16(psx_exp1_t*, uint32_t);
-uint8_t psx_exp1_read8(psx_exp1_t*, uint32_t);
-void psx_exp1_write32(psx_exp1_t*, uint32_t, uint32_t);
-void psx_exp1_write16(psx_exp1_t*, uint32_t, uint16_t);
-void psx_exp1_write8(psx_exp1_t*, uint32_t, uint8_t);
-void psx_exp1_destroy(psx_exp1_t*);
-
+#ifndef EXP1_H
+#define EXP1_H
+
+#include <stdint.h>
+
+#include "mc1.h"
+
+#define PSX_EXP1_BEGIN 0x1f000000
+#define PSX_EXP1_SIZE  0x80000
+#define PSX_EXP1_END   0x1f080000
+
+typedef struct {
+    uint32_t bus_delay;
+    uint32_t io_base, io_size;
+
+    psx_mc1_t* mc1;
+    uint8_t* rom;
+} psx_exp1_t;
+
+psx_exp1_t* psx_exp1_create();
+void psx_exp1_init(psx_exp1_t*, psx_mc1_t*, const char*);
+void psx_exp1_load(psx_exp1_t*, const char*);
+uint32_t psx_exp1_read32(psx_exp1_t*, uint32_t);
+uint16_t psx_exp1_read16(psx_exp1_t*, uint32_t);
+uint8_t psx_exp1_read8(psx_exp1_t*, uint32_t);
+void psx_exp1_write32(psx_exp1_t*, uint32_t, uint32_t);
+void psx_exp1_write16(psx_exp1_t*, uint32_t, uint16_t);
+void psx_exp1_write8(psx_exp1_t*, uint32_t, uint8_t);
+void psx_exp1_destroy(psx_exp1_t*);
+
 #endif
\ No newline at end of file
--- a/psx/dev/exp2.c
+++ b/psx/dev/exp2.c
@@ -1,80 +1,80 @@
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "../log.h"
-#include "exp2.h"
-
-psx_exp2_t* psx_exp2_create() {
-    return (psx_exp2_t*)malloc(sizeof(psx_exp2_t));
-}
-
-void psx_exp2_init(psx_exp2_t* exp2, exp2_tty_tx atcons_tx, exp2_tty_tx duart_tx) {
-    memset(exp2, 0, sizeof(psx_exp2_t));
-
-    exp2->io_base = PSX_EXP2_BEGIN;
-    exp2->io_size = PSX_EXP2_SIZE;
-    exp2->atcons_tx = atcons_tx;
-    exp2->duart_tx = duart_tx;
-}
-
-void psx_exp2_atcons_put(psx_exp2_t* exp2, char c) {
-    exp2->atc_stat |= 0x10;
-    exp2->atc_rx = c;
-}
-
-void psx_exp2_duart_put(psx_exp2_t* exp2, char c) {
-    /* To-do */
-}
-
-uint32_t psx_exp2_read32(psx_exp2_t* exp2, uint32_t offset) {
-    return 0;
-}
-
-uint16_t psx_exp2_read16(psx_exp2_t* exp2, uint32_t offset) {
-    return 0;
-}
-
-uint8_t psx_exp2_read8(psx_exp2_t* exp2, uint32_t offset) {
-    switch (offset) {
-        case EXP2_DTL_ATC_STAT:
-            return exp2->atc_stat | 8;
-
-        case EXP2_DTL_ATC_DATA:
-            exp2->atc_stat &= 0xef;
-            return exp2->atc_rx;
-    }
-
-    return 0;
-}
-
-void psx_exp2_write32(psx_exp2_t* exp2, uint32_t offset, uint32_t value) {
-    log_warn("Unhandled 32-bit EXP2 write at offset %08x (%08x)", offset, value);
-}
-
-void psx_exp2_write16(psx_exp2_t* exp2, uint32_t offset, uint16_t value) {
-    log_warn("Unhandled 16-bit EXP2 write at offset %08x (%04x)", offset, value);
-}
-
-void psx_exp2_write8(psx_exp2_t* exp2, uint32_t offset, uint8_t value) {
-    switch (offset) {
-        case EXP2_DTL_ATC_DATA:
-            if (exp2->atcons_tx)
-                exp2->atcons_tx(exp2->atcons_udata, value);
-            return;
-        break;
-
-        case EXP2_LED:
-        case EXP2_POST:
-        case EXP2_POST2:
-            // To-do: Do something with this data
-            return;
-        break;
-    }
-
-    log_warn("Unhandled 8-bit EXP2 write at offset %08x (%02x)", offset, value);
-}
-
-void psx_exp2_destroy(psx_exp2_t* exp2) {
-    free(exp2);
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../log.h"
+#include "exp2.h"
+
+psx_exp2_t* psx_exp2_create() {
+    return (psx_exp2_t*)malloc(sizeof(psx_exp2_t));
+}
+
+void psx_exp2_init(psx_exp2_t* exp2, exp2_tty_tx atcons_tx, exp2_tty_tx duart_tx) {
+    memset(exp2, 0, sizeof(psx_exp2_t));
+
+    exp2->io_base = PSX_EXP2_BEGIN;
+    exp2->io_size = PSX_EXP2_SIZE;
+    exp2->atcons_tx = atcons_tx;
+    exp2->duart_tx = duart_tx;
+}
+
+void psx_exp2_atcons_put(psx_exp2_t* exp2, char c) {
+    exp2->atc_stat |= 0x10;
+    exp2->atc_rx = c;
+}
+
+void psx_exp2_duart_put(psx_exp2_t* exp2, char c) {
+    /* To-do */
+}
+
+uint32_t psx_exp2_read32(psx_exp2_t* exp2, uint32_t offset) {
+    return 0;
+}
+
+uint16_t psx_exp2_read16(psx_exp2_t* exp2, uint32_t offset) {
+    return 0;
+}
+
+uint8_t psx_exp2_read8(psx_exp2_t* exp2, uint32_t offset) {
+    switch (offset) {
+        case EXP2_DTL_ATC_STAT:
+            return exp2->atc_stat | 8;
+
+        case EXP2_DTL_ATC_DATA:
+            exp2->atc_stat &= 0xef;
+            return exp2->atc_rx;
+    }
+
+    return 0;
+}
+
+void psx_exp2_write32(psx_exp2_t* exp2, uint32_t offset, uint32_t value) {
+    log_warn("Unhandled 32-bit EXP2 write at offset %08x (%08x)", offset, value);
+}
+
+void psx_exp2_write16(psx_exp2_t* exp2, uint32_t offset, uint16_t value) {
+    log_warn("Unhandled 16-bit EXP2 write at offset %08x (%04x)", offset, value);
+}
+
+void psx_exp2_write8(psx_exp2_t* exp2, uint32_t offset, uint8_t value) {
+    switch (offset) {
+        case EXP2_DTL_ATC_DATA:
+            if (exp2->atcons_tx)
+                exp2->atcons_tx(exp2->atcons_udata, value);
+            return;
+        break;
+
+        case EXP2_LED:
+        case EXP2_POST:
+        case EXP2_POST2:
+            // To-do: Do something with this data
+            return;
+        break;
+    }
+
+    log_warn("Unhandled 8-bit EXP2 write at offset %08x (%02x)", offset, value);
+}
+
+void psx_exp2_destroy(psx_exp2_t* exp2) {
+    free(exp2);
 }
\ No newline at end of file
--- a/psx/dev/exp2.h
+++ b/psx/dev/exp2.h
@@ -1,48 +1,48 @@
-#ifndef EXP2_H
-#define EXP2_H
-
-#include <stdint.h>
-
-#define PSX_EXP2_BEGIN 0x1f802000
-#define PSX_EXP2_SIZE  0x1fe000
-#define PSX_EXP2_END   0x1f9fffff
-
-#define EXP2_DTL_ATC_STAT  0x00 // 1f802000
-#define EXP2_DTL_ATC_DATA  0x02 // 1f802002
-#define EXP2_DTL_HDATA     0x04 // 1f802004
-#define EXP2_DTL_SEC_IRQ10 0x30 // 1f802030
-#define EXP2_DTL_IRQ_CTRL  0x32 // 1f802032
-#define EXP2_DTL_BOOT_DIP  0x40 // 1f802040
-#define EXP2_POST          0x41 // 1f802041
-#define EXP2_LED           0x42 // 1f802042
-#define EXP2_POST2         0x70 // 1f802070
-
-typedef void (*exp2_tty_tx)(void*, uint8_t);
-
-typedef struct {
-    uint32_t bus_delay;
-    uint32_t io_base, io_size;
-
-    void* duart_udata;
-    void* atcons_udata;
-
-    exp2_tty_tx duart_tx;
-    exp2_tty_tx atcons_tx;
-
-    uint8_t atc_stat;
-    uint8_t atc_rx;
-} psx_exp2_t;
-
-psx_exp2_t* psx_exp2_create();
-void psx_exp2_init(psx_exp2_t*, exp2_tty_tx atcons_tx, exp2_tty_tx duart_tx);
-void psx_exp2_atcons_put(psx_exp2_t*, char);
-void psx_exp2_duart_put(psx_exp2_t*, char);
-uint32_t psx_exp2_read32(psx_exp2_t*, uint32_t);
-uint16_t psx_exp2_read16(psx_exp2_t*, uint32_t);
-uint8_t psx_exp2_read8(psx_exp2_t*, uint32_t);
-void psx_exp2_write32(psx_exp2_t*, uint32_t, uint32_t);
-void psx_exp2_write16(psx_exp2_t*, uint32_t, uint16_t);
-void psx_exp2_write8(psx_exp2_t*, uint32_t, uint8_t);
-void psx_exp2_destroy(psx_exp2_t*);
-
+#ifndef EXP2_H
+#define EXP2_H
+
+#include <stdint.h>
+
+#define PSX_EXP2_BEGIN 0x1f802000
+#define PSX_EXP2_SIZE  0x1fe000
+#define PSX_EXP2_END   0x1f9fffff
+
+#define EXP2_DTL_ATC_STAT  0x00 // 1f802000
+#define EXP2_DTL_ATC_DATA  0x02 // 1f802002
+#define EXP2_DTL_HDATA     0x04 // 1f802004
+#define EXP2_DTL_SEC_IRQ10 0x30 // 1f802030
+#define EXP2_DTL_IRQ_CTRL  0x32 // 1f802032
+#define EXP2_DTL_BOOT_DIP  0x40 // 1f802040
+#define EXP2_POST          0x41 // 1f802041
+#define EXP2_LED           0x42 // 1f802042
+#define EXP2_POST2         0x70 // 1f802070
+
+typedef void (*exp2_tty_tx)(void*, uint8_t);
+
+typedef struct {
+    uint32_t bus_delay;
+    uint32_t io_base, io_size;
+
+    void* duart_udata;
+    void* atcons_udata;
+
+    exp2_tty_tx duart_tx;
+    exp2_tty_tx atcons_tx;
+
+    uint8_t atc_stat;
+    uint8_t atc_rx;
+} psx_exp2_t;
+
+psx_exp2_t* psx_exp2_create();
+void psx_exp2_init(psx_exp2_t*, exp2_tty_tx atcons_tx, exp2_tty_tx duart_tx);
+void psx_exp2_atcons_put(psx_exp2_t*, char);
+void psx_exp2_duart_put(psx_exp2_t*, char);
+uint32_t psx_exp2_read32(psx_exp2_t*, uint32_t);
+uint16_t psx_exp2_read16(psx_exp2_t*, uint32_t);
+uint8_t psx_exp2_read8(psx_exp2_t*, uint32_t);
+void psx_exp2_write32(psx_exp2_t*, uint32_t, uint32_t);
+void psx_exp2_write16(psx_exp2_t*, uint32_t, uint16_t);
+void psx_exp2_write8(psx_exp2_t*, uint32_t, uint8_t);
+void psx_exp2_destroy(psx_exp2_t*);
+
 #endif
\ No newline at end of file
--- a/psx/dev/gpu.c
+++ b/psx/dev/gpu.c
@@ -1,1746 +1,1743 @@
-#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)
-
-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;
-
-    // Default window size, this is not normally needed
-    gpu->display_mode = 1;
-
-    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 | 0x1c000000;
-    }
-
-    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);
-
-    return 0;
-
-    // exit(1);
-}
-
-uint8_t psx_gpu_read8(psx_gpu_t* gpu, uint32_t offset) {
-    log_fatal("Unhandled 8-bit GPU read at offset %08x", offset);
-
-    return 0;
-
-    // exit(1);
-}
-
-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 = gpu->vram[(tpx + (tx >> 2)) + ((tpy + ty) * 1024)];
-
-            int index = (texel >> ((tx & 0x3) << 2)) & 0xf;
-
-            return gpu->vram[(clutx + index) + (cluty * 1024)];
-        } break;
-
-        // 8-bit
-        case 1: {
-            uint16_t texel = gpu->vram[(tpx + (tx >> 1)) + ((tpy + ty) * 1024)];
-
-            int index = (texel >> ((tx & 0x1) << 3)) & 0xff;
-
-            return gpu->vram[(clutx + index) + (cluty * 1024)];
-        } break;
-
-        // 15-bit
-        default: {
-            return gpu->vram[(tpx + tx) + ((tpy + ty) * 1024)];
-        } 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 = 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++) {
-        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;
-
-            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 = 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);
-                        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);
-            }
-
-            gpu->vram[x + (y * 1024)] = color;
-        }
-    }
-}
-
-void gpu_render_rect(psx_gpu_t* gpu, rect_data_t data) {
-    if ((data.v0.x >= 1024) || (data.v0.y >= 512))
-        return;
-
-    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 = gpu->vram[x + (y * 1024)];
-
-                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);
-            }
-
-            gpu->vram[x + (y * 1024)] = 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 < 1024) && (y < 512) && (x >= 0) && (y >= 0))
-            gpu->vram[x + (y * 1024)] = 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 < 1024) && (y < 512) && (x >= 0) && (y >= 0))
-            gpu->vram[x + (y * 1024)] = 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) {
-    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);
-}
-
-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++) {
-            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) {
-    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_cmd_a0(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
-            gpu->vram[xpos + (ypos * 1024)] = 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;
-            }
-
-            gpu->vram[xpos + (ypos * 1024)] = 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;
-    }
-}
-
-// 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_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: {
-            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 < 1024) && (y < 512) && (x >= 0) && (y >= 0))
-                            gpu->vram[x + (y * 1024)] = 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++) {
-                        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];
-                    }
-                }
-
-                gpu->state = GPU_STATE_RECV_CMD;
-            }
-        } break;
-    }
-}
-
-void psx_gpu_update_cmd(psx_gpu_t* gpu) {
-    int type = (gpu->buf[0] >> 29) & 7;
-
-    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 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 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++;
-
-    psx_ic_irq(gpu->ic, IC_TIMER2);
-
-    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);
-
-    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);
-
-        //psx_ic_irq(gpu->ic, IC_SPU);
-        // psx_ic_irq(gpu->ic, IC_SPU);
-        
-        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);
-}
+#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)
+
+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;
+
+    // Default window size, this is not normally needed
+    gpu->display_mode = 1;
+
+    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 | 0x1c000000;
+    }
+
+    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) {
+    printf("Unhandled 16-bit GPU read at offset %08x\n", offset);
+
+    return 0;
+
+    // exit(1);
+}
+
+uint8_t psx_gpu_read8(psx_gpu_t* gpu, uint32_t offset) {
+    printf("Unhandled 8-bit GPU read at offset %08x\n", offset);
+
+    return 0;
+
+    // exit(1);
+}
+
+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 = gpu->vram[(tpx + (tx >> 2)) + ((tpy + ty) * 1024)];
+
+            int index = (texel >> ((tx & 0x3) << 2)) & 0xf;
+
+            return gpu->vram[(clutx + index) + (cluty * 1024)];
+        } break;
+
+        // 8-bit
+        case 1: {
+            uint16_t texel = gpu->vram[(tpx + (tx >> 1)) + ((tpy + ty) * 1024)];
+
+            int index = (texel >> ((tx & 0x1) << 3)) & 0xff;
+
+            return gpu->vram[(clutx + index) + (cluty * 1024)];
+        } break;
+
+        // 15-bit
+        default: {
+            return gpu->vram[(tpx + tx) + ((tpy + ty) * 1024)];
+        } 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 = 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++) {
+        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;
+
+            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 = 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);
+                        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);
+            }
+
+            gpu->vram[x + (y * 1024)] = color;
+        }
+    }
+}
+
+void gpu_render_rect(psx_gpu_t* gpu, rect_data_t data) {
+    if ((data.v0.x >= 1024) || (data.v0.y >= 512))
+        return;
+
+    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 = gpu->vram[x + (y * 1024)];
+
+                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);
+            }
+
+            gpu->vram[x + (y * 1024)] = 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 < 1024) && (y < 512) && (x >= 0) && (y >= 0))
+            gpu->vram[x + (y * 1024)] = 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 < 1024) && (y < 512) && (x >= 0) && (y >= 0))
+            gpu->vram[x + (y * 1024)] = 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) {
+    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);
+}
+
+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++) {
+            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) {
+    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_cmd_a0(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
+            gpu->vram[xpos + (ypos * 1024)] = 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;
+            }
+
+            gpu->vram[xpos + (ypos * 1024)] = 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;
+    }
+}
+
+// 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_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: {
+            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 < 1024) && (y < 512) && (x >= 0) && (y >= 0))
+                            gpu->vram[x + (y * 1024)] = 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++) {
+                        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];
+                    }
+                }
+
+                gpu->state = GPU_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+
+void psx_gpu_update_cmd(psx_gpu_t* gpu) {
+    int type = (gpu->buf[0] >> 29) & 7;
+
+    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 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 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) {
+    printf("Unhandled 16-bit GPU write at offset %08x (%04x)\n", offset, value);
+}
+
+void psx_gpu_write8(psx_gpu_t* gpu, uint32_t offset, uint8_t value) {
+    printf("Unhandled 8-bit GPU write at offset %08x (%02x)\n", 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++;
+
+    psx_ic_irq(gpu->ic, IC_TIMER2);
+
+    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);
+
+    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);
+}
--- a/psx/dev/gpu.h
+++ b/psx/dev/gpu.h
@@ -1,171 +1,171 @@
-#ifndef GPU_H
-#define GPU_H
-
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "ic.h"
-
-#define PSX_GPU_BEGIN 0x1f801810
-#define PSX_GPU_SIZE  0x8
-#define PSX_GPU_END   0x1f801814
-
-#define PSX_GPU_FB_WIDTH 1024
-#define PSX_GPU_FB_HEIGHT 512
-
-// Use this when updating your texture
-#define PSX_GPU_FB_STRIDE 2048
-
-#define PSX_GPU_VRAM_SIZE (0x100000)
-
-#define PSX_GPU_CLOCK_NTSC 53693175 // 53.693175 MHz
-#define PSX_GPU_CLOCK_FREQ_NTSC 53.693175f // 53.693175 MHz
-#define PSX_GPU_CLOCK_FREQ_PAL 53.203425f // 53.203425 MHz
-
-enum {
-    GPU_EVENT_DMODE,
-    GPU_EVENT_VBLANK,
-    GPU_EVENT_VBLANK_END,
-    GPU_EVENT_HBLANK,
-    GPU_EVENT_HBLANK_END,
-    GPU_EVENT_VBLANK_TIMER
-};
-
-enum {
-    GPU_STATE_RECV_CMD,
-    GPU_STATE_RECV_ARGS,
-    GPU_STATE_RECV_DATA
-};
-
-struct psx_gpu_t;
-
-typedef struct psx_gpu_t psx_gpu_t;
-
-typedef void (*psx_gpu_cmd_t)(psx_gpu_t*);
-typedef void (*psx_gpu_event_callback_t)(psx_gpu_t*);
-
-enum {
-    RS_VARIABLE,
-    RS_1X1,
-    RS_8X8,
-    RS_16X16
-};
-
-enum {
-    RA_RAW      = 0x01,
-    RA_TRANSP   = 0x02,
-    RA_TEXTURED = 0x04
-};
-
-enum {
-    PA_RAW      = 0x01,
-    PA_TRANSP   = 0x02,
-    PA_TEXTURED = 0x04,
-    PA_QUAD     = 0x08,
-    PA_SHADED   = 0x10
-};
-
-typedef struct {
-    int16_t x, y;
-    uint32_t c;
-    uint8_t tx, ty;
-} vertex_t;
-
-typedef struct {
-    uint8_t attrib;
-    vertex_t v[4];
-    uint16_t clut, texp;
-} poly_data_t;
-
-typedef struct {
-    uint8_t attrib;
-    vertex_t v0;
-    uint16_t clut;
-    uint16_t width, height;
-} rect_data_t;
-
-struct psx_gpu_t {
-    uint32_t bus_delay;
-    uint32_t io_base, io_size;
-
-    void* udata[4];
-
-    uint16_t* vram;
-
-    // State data
-    uint32_t buf[16];
-    uint32_t recv_data;
-    int buf_index;
-    int cmd_args_remaining;
-    int cmd_data_remaining;
-
-    // Command counters
-    uint32_t color;
-    uint32_t xpos, ypos;
-    uint32_t xsiz, ysiz;
-    uint32_t tsiz;
-    uint32_t addr;
-    uint32_t xcnt, ycnt;
-    vertex_t v0, v1, v2, v3;
-    uint32_t pal, texp;
-    uint32_t c0_xcnt, c0_ycnt;
-    uint32_t c0_addr;
-    int c0_xsiz, c0_ysiz;
-    int c0_tsiz;
-    int gp1_10h_req;
-
-    // GPU state
-    uint32_t state;
-
-    uint32_t display_mode;
-    uint32_t gpuread;
-    uint32_t gpustat;
-
-    // Drawing area
-    uint32_t draw_x1, draw_y1;
-    uint32_t draw_x2, draw_y2;
-
-    // Drawing offset
-    uint32_t off_x, off_y;
-
-    // Texture Window
-    uint32_t texw_mx, texw_my;
-    uint32_t texw_ox, texw_oy;
-
-    // CLUT offset
-    uint32_t clut_x, clut_y;
-
-    // Texture page
-    uint32_t texp_x, texp_y;
-    uint32_t texp_d;
-
-    // Display area
-    uint32_t disp_x, disp_y;
-    uint32_t disp_x1, disp_x2;
-    uint32_t disp_y1, disp_y2;
-
-    // Timing and IRQs
-    float cycles;
-    int line;
-
-    psx_ic_t* ic;
-
-    psx_gpu_event_callback_t event_cb_table[8];
-};
-
-psx_gpu_t* psx_gpu_create();
-void psx_gpu_init(psx_gpu_t*, psx_ic_t*);
-uint32_t psx_gpu_read32(psx_gpu_t*, uint32_t);
-uint16_t psx_gpu_read16(psx_gpu_t*, uint32_t);
-uint8_t psx_gpu_read8(psx_gpu_t*, uint32_t);
-void psx_gpu_write32(psx_gpu_t*, uint32_t, uint32_t);
-void psx_gpu_write16(psx_gpu_t*, uint32_t, uint16_t);
-void psx_gpu_write8(psx_gpu_t*, uint32_t, uint8_t);
-void psx_gpu_destroy(psx_gpu_t*);
-void psx_gpu_set_udata(psx_gpu_t*, int, void*);
-void psx_gpu_set_event_callback(psx_gpu_t*, int, psx_gpu_event_callback_t);
-void* psx_gpu_get_display_buffer(psx_gpu_t*);
-void psx_gpu_update(psx_gpu_t*, int);
-
+#ifndef GPU_H
+#define GPU_H
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ic.h"
+
+#define PSX_GPU_BEGIN 0x1f801810
+#define PSX_GPU_SIZE  0x8
+#define PSX_GPU_END   0x1f801814
+
+#define PSX_GPU_FB_WIDTH 1024
+#define PSX_GPU_FB_HEIGHT 512
+
+// Use this when updating your texture
+#define PSX_GPU_FB_STRIDE 2048
+
+#define PSX_GPU_VRAM_SIZE (0x100000)
+
+#define PSX_GPU_CLOCK_NTSC 53693175 // 53.693175 MHz
+#define PSX_GPU_CLOCK_FREQ_NTSC 53.693175f // 53.693175 MHz
+#define PSX_GPU_CLOCK_FREQ_PAL 53.203425f // 53.203425 MHz
+
+enum {
+    GPU_EVENT_DMODE,
+    GPU_EVENT_VBLANK,
+    GPU_EVENT_VBLANK_END,
+    GPU_EVENT_HBLANK,
+    GPU_EVENT_HBLANK_END,
+    GPU_EVENT_VBLANK_TIMER
+};
+
+enum {
+    GPU_STATE_RECV_CMD,
+    GPU_STATE_RECV_ARGS,
+    GPU_STATE_RECV_DATA
+};
+
+struct psx_gpu_t;
+
+typedef struct psx_gpu_t psx_gpu_t;
+
+typedef void (*psx_gpu_cmd_t)(psx_gpu_t*);
+typedef void (*psx_gpu_event_callback_t)(psx_gpu_t*);
+
+enum {
+    RS_VARIABLE,
+    RS_1X1,
+    RS_8X8,
+    RS_16X16
+};
+
+enum {
+    RA_RAW      = 0x01,
+    RA_TRANSP   = 0x02,
+    RA_TEXTURED = 0x04
+};
+
+enum {
+    PA_RAW      = 0x01,
+    PA_TRANSP   = 0x02,
+    PA_TEXTURED = 0x04,
+    PA_QUAD     = 0x08,
+    PA_SHADED   = 0x10
+};
+
+typedef struct {
+    int16_t x, y;
+    uint32_t c;
+    uint8_t tx, ty;
+} vertex_t;
+
+typedef struct {
+    uint8_t attrib;
+    vertex_t v[4];
+    uint16_t clut, texp;
+} poly_data_t;
+
+typedef struct {
+    uint8_t attrib;
+    vertex_t v0;
+    uint16_t clut;
+    uint16_t width, height;
+} rect_data_t;
+
+struct psx_gpu_t {
+    uint32_t bus_delay;
+    uint32_t io_base, io_size;
+
+    void* udata[4];
+
+    uint16_t* vram;
+
+    // State data
+    uint32_t buf[16];
+    uint32_t recv_data;
+    int buf_index;
+    int cmd_args_remaining;
+    int cmd_data_remaining;
+
+    // Command counters
+    uint32_t color;
+    uint32_t xpos, ypos;
+    uint32_t xsiz, ysiz;
+    uint32_t tsiz;
+    uint32_t addr;
+    uint32_t xcnt, ycnt;
+    vertex_t v0, v1, v2, v3;
+    uint32_t pal, texp;
+    uint32_t c0_xcnt, c0_ycnt;
+    uint32_t c0_addr;
+    int c0_xsiz, c0_ysiz;
+    int c0_tsiz;
+    int gp1_10h_req;
+
+    // GPU state
+    uint32_t state;
+
+    uint32_t display_mode;
+    uint32_t gpuread;
+    uint32_t gpustat;
+
+    // Drawing area
+    uint32_t draw_x1, draw_y1;
+    uint32_t draw_x2, draw_y2;
+
+    // Drawing offset
+    uint32_t off_x, off_y;
+
+    // Texture Window
+    uint32_t texw_mx, texw_my;
+    uint32_t texw_ox, texw_oy;
+
+    // CLUT offset
+    uint32_t clut_x, clut_y;
+
+    // Texture page
+    uint32_t texp_x, texp_y;
+    uint32_t texp_d;
+
+    // Display area
+    uint32_t disp_x, disp_y;
+    uint32_t disp_x1, disp_x2;
+    uint32_t disp_y1, disp_y2;
+
+    // Timing and IRQs
+    float cycles;
+    int line;
+
+    psx_ic_t* ic;
+
+    psx_gpu_event_callback_t event_cb_table[8];
+};
+
+psx_gpu_t* psx_gpu_create();
+void psx_gpu_init(psx_gpu_t*, psx_ic_t*);
+uint32_t psx_gpu_read32(psx_gpu_t*, uint32_t);
+uint16_t psx_gpu_read16(psx_gpu_t*, uint32_t);
+uint8_t psx_gpu_read8(psx_gpu_t*, uint32_t);
+void psx_gpu_write32(psx_gpu_t*, uint32_t, uint32_t);
+void psx_gpu_write16(psx_gpu_t*, uint32_t, uint16_t);
+void psx_gpu_write8(psx_gpu_t*, uint32_t, uint8_t);
+void psx_gpu_destroy(psx_gpu_t*);
+void psx_gpu_set_udata(psx_gpu_t*, int, void*);
+void psx_gpu_set_event_callback(psx_gpu_t*, int, psx_gpu_event_callback_t);
+void* psx_gpu_get_display_buffer(psx_gpu_t*);
+void psx_gpu_update(psx_gpu_t*, int);
+
 #endif
\ No newline at end of file
--- a/psx/dev/ic.c
+++ b/psx/dev/ic.c
@@ -1,104 +1,119 @@
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "ic.h"
-
-#include "../log.h"
-
-psx_ic_t* psx_ic_create() {
-    return (psx_ic_t*)malloc(sizeof(psx_ic_t));
-}
-
-void psx_ic_init(psx_ic_t* ic, psx_cpu_t* cpu) {
-    memset(ic, 0, sizeof(psx_ic_t));
-
-    ic->io_base = PSX_IC_BEGIN;
-    ic->io_size = PSX_IC_SIZE;
-
-    ic->stat = 0x00000000;
-    ic->mask = 0x00000000;
-
-    ic->cpu = cpu;
-}
-
-uint32_t psx_ic_read32(psx_ic_t* ic, uint32_t offset) {
-    switch (offset) {
-        case 0x00: return ic->stat;
-        case 0x04: return ic->mask;
-    }
-
-    log_fatal("Unhandled 32-bit IC read at offset %08x", offset);
-
-    return 0x0;
-}
-
-uint16_t psx_ic_read16(psx_ic_t* ic, uint32_t offset) {
-    switch (offset) {
-        case 0x00: return ic->stat;
-        case 0x04: return ic->mask;
-    }
-
-    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_fatal("Unhandled 8-bit IC read at offset %08x", offset);
-
-    exit(0);
-
-    return 0x0;
-}
-
-void psx_ic_write32(psx_ic_t* ic, uint32_t offset, uint32_t value) {
-    switch (offset) {
-        case 0x00: ic->stat &= value; break;
-        case 0x04: ic->mask = value; break;
-
-        default: {
-            log_fatal("Unhandled 32-bit IC write at offset %08x (%08x)", offset, value);
-        } break;
-    }
-
-    // Emulate acknowledge
-    if (!(ic->stat & ic->mask)) {
-        ic->cpu->cop0_r[COP0_CAUSE] &= ~SR_IM2;
-    }
-}
-
-void psx_ic_write16(psx_ic_t* ic, uint32_t offset, uint16_t value) {
-    switch (offset) {
-        case 0x00: ic->stat &= (uint32_t)value; break;
-        case 0x04: ic->mask &= 0xffff0000; ic->mask |= (uint32_t)value; break;
-
-        default: {
-            
-        } break;
-    }
-
-    // Emulate acknowledge
-    if (!(ic->stat & ic->mask)) {
-        ic->cpu->cop0_r[COP0_CAUSE] &= ~SR_IM2;
-    }
-}
-
-void psx_ic_write8(psx_ic_t* ic, uint32_t offset, uint8_t value) {
-    printf("Unhandled 8-bit IC write at offset %08x (%02x)\n", offset, value);
-
-    exit(1);
-}
-
-void psx_ic_irq(psx_ic_t* ic, int id) {
-    ic->stat |= id;
-
-    if (ic->mask & ic->stat)
-        psx_cpu_set_irq_pending(ic->cpu);
-}
-
-void psx_ic_destroy(psx_ic_t* ic) {
-    free(ic);
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ic.h"
+
+#include "../log.h"
+
+psx_ic_t* psx_ic_create() {
+    return (psx_ic_t*)malloc(sizeof(psx_ic_t));
+}
+
+void psx_ic_init(psx_ic_t* ic, psx_cpu_t* cpu) {
+    memset(ic, 0, sizeof(psx_ic_t));
+
+    ic->io_base = PSX_IC_BEGIN;
+    ic->io_size = PSX_IC_SIZE;
+
+    ic->stat = 0x00000000;
+    ic->mask = 0x00000000;
+
+    ic->cpu = cpu;
+}
+
+uint32_t psx_ic_read32(psx_ic_t* ic, uint32_t offset) {
+    switch (offset) {
+        case 0x00: return ic->stat;
+        case 0x04: return ic->mask;
+    }
+
+    log_fatal("Unhandled 32-bit IC read at offset %08x", offset);
+
+    return 0x0;
+}
+
+uint16_t psx_ic_read16(psx_ic_t* ic, uint32_t offset) {
+    switch (offset) {
+        case 0x00: return (ic->stat >> 0 ) & 0xffff;
+        case 0x02: return (ic->stat >> 16) & 0xffff;
+        case 0x04: return (ic->mask >> 0 ) & 0xffff;
+        case 0x06: return (ic->mask >> 16) & 0xffff;
+    }
+
+    return 0x0;
+}
+
+uint8_t psx_ic_read8(psx_ic_t* ic, uint32_t offset) {
+    switch (offset) {
+        case 0x00: return (ic->stat >> 0 ) & 0xff;
+        case 0x01: return (ic->stat >> 8 ) & 0xff;
+        case 0x02: return (ic->stat >> 16) & 0xff;
+        case 0x03: return (ic->stat >> 24) & 0xff;
+        case 0x04: return (ic->mask >> 0 ) & 0xff;
+        case 0x05: return (ic->mask >> 8 ) & 0xff;
+        case 0x06: return (ic->mask >> 16) & 0xff;
+        case 0x07: return (ic->mask >> 24) & 0xff;
+    }
+
+    return 0x0;
+}
+
+void psx_ic_write32(psx_ic_t* ic, uint32_t offset, uint32_t value) {
+    switch (offset) {
+        case 0x00: ic->stat &= value; break;
+        case 0x04: ic->mask = value; break;
+
+        default: {
+            log_fatal("Unhandled 32-bit IC write at offset %08x (%08x)", offset, value);
+        } break;
+    }
+
+    // Emulate acknowledge
+    if (!(ic->stat & ic->mask)) {
+        ic->cpu->cop0_r[COP0_CAUSE] &= ~SR_IM2;
+    }
+}
+
+void psx_ic_write16(psx_ic_t* ic, uint32_t offset, uint16_t value) {
+    switch (offset) {
+        case 0x00: ic->stat &= ((uint32_t)value) << 0 ; break;
+        case 0x02: ic->stat &= ((uint32_t)value) << 16; break;
+        case 0x04: ic->mask  = ((uint32_t)value) << 0 ; break;
+        case 0x06: ic->mask  = ((uint32_t)value) << 16; break;
+    }
+
+    // Emulate acknowledge
+    if (!(ic->stat & ic->mask)) {
+        ic->cpu->cop0_r[COP0_CAUSE] &= ~SR_IM2;
+    }
+}
+
+void psx_ic_write8(psx_ic_t* ic, uint32_t offset, uint8_t value) {
+    switch (offset) {
+        case 0x00: ic->stat &= ((uint32_t)value) << 0 ; break;
+        case 0x01: ic->stat &= ((uint32_t)value) << 8 ; break;
+        case 0x02: ic->stat &= ((uint32_t)value) << 16; break;
+        case 0x03: ic->stat &= ((uint32_t)value) << 24; break;
+        case 0x04: ic->mask  = ((uint32_t)value) << 0 ; break;
+        case 0x05: ic->mask  = ((uint32_t)value) << 8 ; break;
+        case 0x06: ic->mask  = ((uint32_t)value) << 16; break;
+        case 0x07: ic->mask  = ((uint32_t)value) << 24; break;
+    }
+
+    // Emulate acknowledge
+    if (!(ic->stat & ic->mask)) {
+        ic->cpu->cop0_r[COP0_CAUSE] &= ~SR_IM2;
+    }
+}
+
+void psx_ic_irq(psx_ic_t* ic, int id) {
+    ic->stat |= id;
+
+    if (ic->mask & ic->stat)
+        psx_cpu_set_irq_pending(ic->cpu);
+}
+
+void psx_ic_destroy(psx_ic_t* ic) {
+    free(ic);
 }
\ No newline at end of file
--- a/psx/dev/ic.h
+++ b/psx/dev/ic.h
@@ -1,67 +1,67 @@
-#ifndef IC_H
-#define IC_H
-
-#include <stdint.h>
-
-#include "../cpu.h"
-
-#define PSX_IC_BEGIN 0x1f801070
-#define PSX_IC_SIZE  0x8
-#define PSX_IC_END   0x1F801077
-
-/*
-    0     IRQ0 VBLANK (PAL=50Hz, NTSC=60Hz)
-    1     IRQ1 GPU   Can be requested via GP0(1Fh) command (rarely used)
-    2     IRQ2 CDROM
-    3     IRQ3 DMA
-    4     IRQ4 TMR0  Timer 0 aka Root Counter 0 (Sysclk or Dotclk)
-    5     IRQ5 TMR1  Timer 1 aka Root Counter 1 (Sysclk or H-blank)
-    6     IRQ6 TMR2  Timer 2 aka Root Counter 2 (Sysclk or Sysclk/8)
-    7     IRQ7 Controller and Memory Card - Byte Received Interrupt
-    8     IRQ8 SIO
-    9     IRQ9 SPU
-    10    IRQ10 Controller - Lightpen Interrupt (reportedly also PIO...?)
-    11-15 Not used (always zero)
-    16-31 Garbage
-*/
-enum {
-    IC_VBLANK       = 0x001,
-    IC_GPU          = 0x002,
-    IC_CDROM        = 0x004,
-    IC_DMA          = 0x008,
-    IC_TIMER0       = 0x010,
-    IC_TIMER1       = 0x020,
-    IC_TIMER2       = 0x040,
-    IC_JOY          = 0x080,
-    IC_SIO          = 0x100,
-    IC_SPU          = 0x200,
-    IC_LP_PIO       = 0x400
-};
-
-/*
-    1F801070h 2    I_STAT - Interrupt status register
-    1F801074h 2    I_MASK - Interrupt mask register
-*/
-
-typedef struct {
-    uint32_t bus_delay;
-    uint32_t io_base, io_size;
-
-    uint16_t stat;
-    uint16_t mask;
-
-    psx_cpu_t* cpu;
-} psx_ic_t;
-
-psx_ic_t* psx_ic_create();
-void psx_ic_init(psx_ic_t*, psx_cpu_t*);
-uint32_t psx_ic_read32(psx_ic_t*, uint32_t);
-uint16_t psx_ic_read16(psx_ic_t*, uint32_t);
-uint8_t psx_ic_read8(psx_ic_t*, uint32_t);
-void psx_ic_write32(psx_ic_t*, uint32_t, uint32_t);
-void psx_ic_write16(psx_ic_t*, uint32_t, uint16_t);
-void psx_ic_write8(psx_ic_t*, uint32_t, uint8_t);
-void psx_ic_irq(psx_ic_t*, int);
-void psx_ic_destroy(psx_ic_t*);
-
+#ifndef IC_H
+#define IC_H
+
+#include <stdint.h>
+
+#include "../cpu.h"
+
+#define PSX_IC_BEGIN 0x1f801070
+#define PSX_IC_SIZE  0x8
+#define PSX_IC_END   0x1F801077
+
+/*
+    0     IRQ0 VBLANK (PAL=50Hz, NTSC=60Hz)
+    1     IRQ1 GPU   Can be requested via GP0(1Fh) command (rarely used)
+    2     IRQ2 CDROM
+    3     IRQ3 DMA
+    4     IRQ4 TMR0  Timer 0 aka Root Counter 0 (Sysclk or Dotclk)
+    5     IRQ5 TMR1  Timer 1 aka Root Counter 1 (Sysclk or H-blank)
+    6     IRQ6 TMR2  Timer 2 aka Root Counter 2 (Sysclk or Sysclk/8)
+    7     IRQ7 Controller and Memory Card - Byte Received Interrupt
+    8     IRQ8 SIO
+    9     IRQ9 SPU
+    10    IRQ10 Controller - Lightpen Interrupt (reportedly also PIO...?)
+    11-15 Not used (always zero)
+    16-31 Garbage
+*/
+enum {
+    IC_VBLANK       = 0x001,
+    IC_GPU          = 0x002,
+    IC_CDROM        = 0x004,
+    IC_DMA          = 0x008,
+    IC_TIMER0       = 0x010,
+    IC_TIMER1       = 0x020,
+    IC_TIMER2       = 0x040,
+    IC_JOY          = 0x080,
+    IC_SIO          = 0x100,
+    IC_SPU          = 0x200,
+    IC_LP_PIO       = 0x400
+};
+
+/*
+    1F801070h 2    I_STAT - Interrupt status register
+    1F801074h 2    I_MASK - Interrupt mask register
+*/
+
+typedef struct {
+    uint32_t bus_delay;
+    uint32_t io_base, io_size;
+
+    uint16_t stat;
+    uint16_t mask;
+
+    psx_cpu_t* cpu;
+} psx_ic_t;
+
+psx_ic_t* psx_ic_create();
+void psx_ic_init(psx_ic_t*, psx_cpu_t*);
+uint32_t psx_ic_read32(psx_ic_t*, uint32_t);
+uint16_t psx_ic_read16(psx_ic_t*, uint32_t);
+uint8_t psx_ic_read8(psx_ic_t*, uint32_t);
+void psx_ic_write32(psx_ic_t*, uint32_t, uint32_t);
+void psx_ic_write16(psx_ic_t*, uint32_t, uint16_t);
+void psx_ic_write8(psx_ic_t*, uint32_t, uint8_t);
+void psx_ic_irq(psx_ic_t*, int);
+void psx_ic_destroy(psx_ic_t*);
+
 #endif
\ No newline at end of file
--- a/psx/dev/input.c
+++ b/psx/dev/input.c
@@ -1,42 +1,42 @@
-#ifndef PAD_H
-#define PAD_H
-
-#include "input.h"
-
-#include <string.h>
-#include <stdlib.h>
-
-psx_input_t* psx_input_create() {
-    return (psx_input_t*)malloc(sizeof(psx_input_t));
-}
-
-void psx_input_init(psx_input_t* input) {
-    memset(input, 0, sizeof(psx_input_t));
-}
-
-void psx_input_set_write_func(psx_input_t* input, psx_input_write_t write_func) {
-    input->write_func = write_func;
-}
-
-void psx_input_set_read_func(psx_input_t* input, psx_input_read_t read_func) {
-    input->read_func = read_func;
-}
-
-void psx_input_set_on_button_press_func(psx_input_t* input, psx_input_on_button_press_t on_button_press_func) {
-    input->on_button_press_func = on_button_press_func;
-}
-
-void psx_input_set_on_button_release_func(psx_input_t* input, psx_input_on_button_release_t on_button_release_func) {
-    input->on_button_release_func = on_button_release_func;
-}
-
-void psx_input_set_on_analog_change_func(psx_input_t* input, psx_input_on_analog_change_t on_analog_change_func) {
-    input->on_analog_change_func = on_analog_change_func;
-}
-
-void psx_input_destroy(psx_input_t* input) {
-    free(input->udata);
-    free(input);
-}
-
+#ifndef PAD_H
+#define PAD_H
+
+#include "input.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+psx_input_t* psx_input_create() {
+    return (psx_input_t*)malloc(sizeof(psx_input_t));
+}
+
+void psx_input_init(psx_input_t* input) {
+    memset(input, 0, sizeof(psx_input_t));
+}
+
+void psx_input_set_write_func(psx_input_t* input, psx_input_write_t write_func) {
+    input->write_func = write_func;
+}
+
+void psx_input_set_read_func(psx_input_t* input, psx_input_read_t read_func) {
+    input->read_func = read_func;
+}
+
+void psx_input_set_on_button_press_func(psx_input_t* input, psx_input_on_button_press_t on_button_press_func) {
+    input->on_button_press_func = on_button_press_func;
+}
+
+void psx_input_set_on_button_release_func(psx_input_t* input, psx_input_on_button_release_t on_button_release_func) {
+    input->on_button_release_func = on_button_release_func;
+}
+
+void psx_input_set_on_analog_change_func(psx_input_t* input, psx_input_on_analog_change_t on_analog_change_func) {
+    input->on_analog_change_func = on_analog_change_func;
+}
+
+void psx_input_destroy(psx_input_t* input) {
+    free(input->udata);
+    free(input);
+}
+
 #endif
\ No newline at end of file
--- a/psx/dev/input.h
+++ b/psx/dev/input.h
@@ -1,38 +1,38 @@
-#ifndef INPUT_H
-#define INPUT_H
-
-#include <stdint.h>
-
-struct psx_input_t;
-
-typedef struct psx_input_t psx_input_t;
-
-typedef void (*psx_input_write_t)(void*, uint16_t);
-typedef uint32_t (*psx_input_read_t)(void*);
-typedef void (*psx_input_on_button_press_t)(void*, uint16_t);
-typedef void (*psx_input_on_button_release_t)(void*, uint16_t);
-typedef void (*psx_input_on_analog_change_t)(void*, uint16_t);
-typedef int (*psx_input_query_fifo_t)(void*);
-
-struct psx_input_t {
-    void* udata;
-
-    psx_input_write_t write_func;
-    psx_input_read_t read_func;
-    psx_input_on_button_press_t on_button_press_func;
-    psx_input_on_button_release_t on_button_release_func;
-    psx_input_on_analog_change_t on_analog_change_func;
-    psx_input_query_fifo_t query_fifo_func;
-};
-
-psx_input_t* psx_input_create();
-void psx_input_init(psx_input_t*);
-void psx_input_set_write_func(psx_input_t*, psx_input_write_t);
-void psx_input_set_read_func(psx_input_t*, psx_input_read_t);
-void psx_input_set_on_button_press_func(psx_input_t*, psx_input_on_button_press_t);
-void psx_input_set_on_button_release_func(psx_input_t*, psx_input_on_button_release_t);
-void psx_input_set_on_analog_change_func(psx_input_t*, psx_input_on_analog_change_t);
-void psx_input_set_query_fifo_func(psx_input_t*, psx_input_query_fifo_t);
-void psx_input_destroy(psx_input_t*);
-
+#ifndef INPUT_H
+#define INPUT_H
+
+#include <stdint.h>
+
+struct psx_input_t;
+
+typedef struct psx_input_t psx_input_t;
+
+typedef void (*psx_input_write_t)(void*, uint16_t);
+typedef uint32_t (*psx_input_read_t)(void*);
+typedef void (*psx_input_on_button_press_t)(void*, uint16_t);
+typedef void (*psx_input_on_button_release_t)(void*, uint16_t);
+typedef void (*psx_input_on_analog_change_t)(void*, uint16_t);
+typedef int (*psx_input_query_fifo_t)(void*);
+
+struct psx_input_t {
+    void* udata;
+
+    psx_input_write_t write_func;
+    psx_input_read_t read_func;
+    psx_input_on_button_press_t on_button_press_func;
+    psx_input_on_button_release_t on_button_release_func;
+    psx_input_on_analog_change_t on_analog_change_func;
+    psx_input_query_fifo_t query_fifo_func;
+};
+
+psx_input_t* psx_input_create();
+void psx_input_init(psx_input_t*);
+void psx_input_set_write_func(psx_input_t*, psx_input_write_t);
+void psx_input_set_read_func(psx_input_t*, psx_input_read_t);
+void psx_input_set_on_button_press_func(psx_input_t*, psx_input_on_button_press_t);
+void psx_input_set_on_button_release_func(psx_input_t*, psx_input_on_button_release_t);
+void psx_input_set_on_analog_change_func(psx_input_t*, psx_input_on_analog_change_t);
+void psx_input_set_query_fifo_func(psx_input_t*, psx_input_query_fifo_t);
+void psx_input_destroy(psx_input_t*);
+
 #endif
\ No newline at end of file
--- a/psx/dev/mc1.c
+++ b/psx/dev/mc1.c
@@ -1,246 +1,246 @@
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "mc1.h"
-#include "../log.h"
-
-/*
-  0-3   Write Delay        (00h..0Fh=01h..10h Cycles)
-  4-7   Read Delay         (00h..0Fh=01h..10h Cycles)
-  8     Recovery Period    (0=No, 1=Yes, uses COM0 timings)
-  9     Hold Period        (0=No, 1=Yes, uses COM1 timings)
-  10    Floating Period    (0=No, 1=Yes, uses COM2 timings)
-  11    Pre-strobe Period  (0=No, 1=Yes, uses COM3 timings)
-  12    Data Bus-width     (0=8bits, 1=16bits)
-  13    Auto Increment     (0=No, 1=Yes)
-  14-15 Unknown (R/W)
-  16-20 Memory Window Size (1 SHL N bytes) (0..1Fh = 1 byte ... 2 gigabytes)
-  21-23 Unknown (always zero)
-  24-27 DMA timing override
-  28    Address error flag. Write 1 to it to clear it.
-  29    DMA timing select  (0=use normal timings, 1=use bits 24-27)
-  30    Wide DMA           (0=use bit 12, 1=override to full 32 bits)
-  31    Wait               (1=wait on external device before being ready)
-*/
-
-#define DEFAULT_DLY 2
-
-// #define WRITE_DLY(dev) ((mc1-> ## dev ## _delay & 0xf) + 1)
-// #define READ_DLY(dev) (((mc1-> ## dev ## _delay >> 4) & 0xf) + 1)
-// #define USE_COM0(dev) (mc1-> ## dev ## _delay >> 8)
-// #define USE_COM1(dev) (mc1-> ## dev ## _delay >> 9)
-// #define USE_COM2(dev) (mc1-> ## dev ## _delay >> 10)
-// #define USE_COM3(dev) (mc1-> ## dev ## _delay >> 11)
-
-// #define COM0_DLY (mc1->com_delay )
-
-// psx_access_delay_t mc1_get_com_delay(int uc0, int uc1, int uc2, int uc3) {
-//     int fst = 0, seq = 0, min = 0;
-// }
-
-// #define COM_DLY(dev)
-
-psx_mc1_t* psx_mc1_create() {
-    return (psx_mc1_t*)malloc(sizeof(psx_mc1_t));
-}
-
-/*
-  1F801000h 4    Expansion 1 Base Address (usually 1F000000h)
-  1F801004h 4    Expansion 2 Base Address (usually 1F802000h)
-  1F801008h 4    Expansion 1 Delay/Size (usually 0013243Fh; 512Kbytes 8bit-bus)
-  1F80100Ch 4    Expansion 3 Delay/Size (usually 00003022h; 1 byte)
-  1F801010h 4    BIOS ROM    Delay/Size (usually 0013243Fh; 512Kbytes 8bit-bus)
-  1F801014h 4    SPU_DELAY   Delay/Size (usually 200931E1h)
-  1F801018h 4    CDROM_DELAY Delay/Size (usually 00020843h or 00020943h)
-  1F80101Ch 4    Expansion 2 Delay/Size (usually 00070777h; 128-bytes 8bit-bus)
-  1F801020h 4    COM_DELAY / COMMON_DELAY (00031125h or 0000132Ch or 00001325h)
-*/
-void psx_mc1_init(psx_mc1_t* mc1) {
-    memset(mc1, 0, sizeof(psx_mc1_t));
-
-    mc1->io_base = PSX_MC1_BEGIN;
-    mc1->io_size = PSX_MC1_SIZE;
-
-    mc1->exp1_base   = 0x1f000000;
-    mc1->exp2_base   = 0x1f802000;
-    mc1->exp1_delay  = 0x0013243f;
-    mc1->exp3_delay  = 0x00003022;
-    mc1->bios_delay  = 0x0013243f;
-    mc1->spu_delay   = 0x200931e1;
-    mc1->cdrom_delay = 0x00020843;
-    mc1->exp2_delay  = 0x00070777;
-    mc1->com_delay   = 0x00031125;
-}
-
-uint32_t psx_mc1_read32(psx_mc1_t* mc1, uint32_t offset) {
-    switch (offset) {
-        case 0x00: return mc1->exp1_base;
-        case 0x04: return mc1->exp2_base;
-        case 0x08: return mc1->exp1_delay;
-        case 0x0c: return mc1->exp3_delay;
-        case 0x10: return mc1->bios_delay;
-        case 0x14: return mc1->spu_delay;
-        case 0x18: return mc1->cdrom_delay;
-        case 0x1c: return mc1->exp2_delay;
-        case 0x20: return mc1->com_delay;
-    }
-
-    log_warn("Unhandled 32-bit MC1 read at offset %08x", offset);
-
-    return 0x0;
-}
-
-uint16_t psx_mc1_read16(psx_mc1_t* mc1, uint32_t offset) {
-    log_warn("Unhandled 16-bit MC1 read at offset %08x", offset);
-
-    return 0x0;
-}
-
-uint8_t psx_mc1_read8(psx_mc1_t* mc1, uint32_t offset) {
-    log_warn("Unhandled 8-bit MC1 read at offset %08x", offset);
-
-    return 0x0;
-}
-
-void psx_mc1_write32(psx_mc1_t* mc1, uint32_t offset, uint32_t value) {
-    switch (offset) {
-        case 0x00: mc1->exp1_base = value; break;
-        case 0x04: mc1->exp2_base = value; break;
-        case 0x08: mc1->exp1_delay = value; break;
-        case 0x0c: mc1->exp3_delay = value; break;
-        case 0x10: mc1->bios_delay = value; break;
-        case 0x14: mc1->spu_delay = value; break;
-        case 0x18: mc1->cdrom_delay = value; break;
-        case 0x1c: mc1->exp2_delay = value; break;
-        case 0x20: mc1->com_delay = value; break;
-
-        default: {
-            log_warn("Unhandled 32-bit MC1 write at offset %08x (%08x)", offset, value);
-        } break;
-    }
-}
-
-void psx_mc1_write16(psx_mc1_t* mc1, uint32_t offset, uint16_t value) {
-    log_warn("Unhandled 16-bit MC1 write at offset %08x (%04x)", offset, value);
-}
-
-void psx_mc1_write8(psx_mc1_t* mc1, uint32_t offset, uint8_t value) {
-    log_warn("Unhandled 8-bit MC1 write at offset %08x (%02x)", offset, value);
-}
-
-uint32_t psx_mc1_get_bios_read_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_ram_read_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_dma_read_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_exp1_read_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_mc1_read_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_mc2_read_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_mc3_read_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_ic_read_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_scratchpad_read_delay(psx_mc1_t* mc1) {
-    return 1;
-}
-
-uint32_t psx_mc1_get_gpu_read_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_spu_read_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_timer_read_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_cdrom_read_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_pad_read_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_bios_write_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_ram_write_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_dma_write_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_exp1_write_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_mc1_write_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_mc2_write_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_mc3_write_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_ic_write_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_scratchpad_write_delay(psx_mc1_t* mc1) {
-    return 1;
-}
-
-uint32_t psx_mc1_get_gpu_write_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_spu_write_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_timer_write_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_cdrom_write_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-uint32_t psx_mc1_get_pad_write_delay(psx_mc1_t* mc1) {
-    return DEFAULT_DLY;
-}
-
-void psx_mc1_destroy(psx_mc1_t* mc1) {
-    free(mc1);
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mc1.h"
+#include "../log.h"
+
+/*
+  0-3   Write Delay        (00h..0Fh=01h..10h Cycles)
+  4-7   Read Delay         (00h..0Fh=01h..10h Cycles)
+  8     Recovery Period    (0=No, 1=Yes, uses COM0 timings)
+  9     Hold Period        (0=No, 1=Yes, uses COM1 timings)
+  10    Floating Period    (0=No, 1=Yes, uses COM2 timings)
+  11    Pre-strobe Period  (0=No, 1=Yes, uses COM3 timings)
+  12    Data Bus-width     (0=8bits, 1=16bits)
+  13    Auto Increment     (0=No, 1=Yes)
+  14-15 Unknown (R/W)
+  16-20 Memory Window Size (1 SHL N bytes) (0..1Fh = 1 byte ... 2 gigabytes)
+  21-23 Unknown (always zero)
+  24-27 DMA timing override
+  28    Address error flag. Write 1 to it to clear it.
+  29    DMA timing select  (0=use normal timings, 1=use bits 24-27)
+  30    Wide DMA           (0=use bit 12, 1=override to full 32 bits)
+  31    Wait               (1=wait on external device before being ready)
+*/
+
+#define DEFAULT_DLY 2
+
+// #define WRITE_DLY(dev) ((mc1-> ## dev ## _delay & 0xf) + 1)
+// #define READ_DLY(dev) (((mc1-> ## dev ## _delay >> 4) & 0xf) + 1)
+// #define USE_COM0(dev) (mc1-> ## dev ## _delay >> 8)
+// #define USE_COM1(dev) (mc1-> ## dev ## _delay >> 9)
+// #define USE_COM2(dev) (mc1-> ## dev ## _delay >> 10)
+// #define USE_COM3(dev) (mc1-> ## dev ## _delay >> 11)
+
+// #define COM0_DLY (mc1->com_delay )
+
+// psx_access_delay_t mc1_get_com_delay(int uc0, int uc1, int uc2, int uc3) {
+//     int fst = 0, seq = 0, min = 0;
+// }
+
+// #define COM_DLY(dev)
+
+psx_mc1_t* psx_mc1_create() {
+    return (psx_mc1_t*)malloc(sizeof(psx_mc1_t));
+}
+
+/*
+  1F801000h 4    Expansion 1 Base Address (usually 1F000000h)
+  1F801004h 4    Expansion 2 Base Address (usually 1F802000h)
+  1F801008h 4    Expansion 1 Delay/Size (usually 0013243Fh; 512Kbytes 8bit-bus)
+  1F80100Ch 4    Expansion 3 Delay/Size (usually 00003022h; 1 byte)
+  1F801010h 4    BIOS ROM    Delay/Size (usually 0013243Fh; 512Kbytes 8bit-bus)
+  1F801014h 4    SPU_DELAY   Delay/Size (usually 200931E1h)
+  1F801018h 4    CDROM_DELAY Delay/Size (usually 00020843h or 00020943h)
+  1F80101Ch 4    Expansion 2 Delay/Size (usually 00070777h; 128-bytes 8bit-bus)
+  1F801020h 4    COM_DELAY / COMMON_DELAY (00031125h or 0000132Ch or 00001325h)
+*/
+void psx_mc1_init(psx_mc1_t* mc1) {
+    memset(mc1, 0, sizeof(psx_mc1_t));
+
+    mc1->io_base = PSX_MC1_BEGIN;
+    mc1->io_size = PSX_MC1_SIZE;
+
+    mc1->exp1_base   = 0x1f000000;
+    mc1->exp2_base   = 0x1f802000;
+    mc1->exp1_delay  = 0x0013243f;
+    mc1->exp3_delay  = 0x00003022;
+    mc1->bios_delay  = 0x0013243f;
+    mc1->spu_delay   = 0x200931e1;
+    mc1->cdrom_delay = 0x00020843;
+    mc1->exp2_delay  = 0x00070777;
+    mc1->com_delay   = 0x00031125;
+}
+
+uint32_t psx_mc1_read32(psx_mc1_t* mc1, uint32_t offset) {
+    switch (offset) {
+        case 0x00: return mc1->exp1_base;
+        case 0x04: return mc1->exp2_base;
+        case 0x08: return mc1->exp1_delay;
+        case 0x0c: return mc1->exp3_delay;
+        case 0x10: return mc1->bios_delay;
+        case 0x14: return mc1->spu_delay;
+        case 0x18: return mc1->cdrom_delay;
+        case 0x1c: return mc1->exp2_delay;
+        case 0x20: return mc1->com_delay;
+    }
+
+    log_warn("Unhandled 32-bit MC1 read at offset %08x", offset);
+
+    return 0x0;
+}
+
+uint16_t psx_mc1_read16(psx_mc1_t* mc1, uint32_t offset) {
+    log_warn("Unhandled 16-bit MC1 read at offset %08x", offset);
+
+    return 0x0;
+}
+
+uint8_t psx_mc1_read8(psx_mc1_t* mc1, uint32_t offset) {
+    log_warn("Unhandled 8-bit MC1 read at offset %08x", offset);
+
+    return 0x0;
+}
+
+void psx_mc1_write32(psx_mc1_t* mc1, uint32_t offset, uint32_t value) {
+    switch (offset) {
+        case 0x00: mc1->exp1_base = value; break;
+        case 0x04: mc1->exp2_base = value; break;
+        case 0x08: mc1->exp1_delay = value; break;
+        case 0x0c: mc1->exp3_delay = value; break;
+        case 0x10: mc1->bios_delay = value; break;
+        case 0x14: mc1->spu_delay = value; break;
+        case 0x18: mc1->cdrom_delay = value; break;
+        case 0x1c: mc1->exp2_delay = value; break;
+        case 0x20: mc1->com_delay = value; break;
+
+        default: {
+            log_warn("Unhandled 32-bit MC1 write at offset %08x (%08x)", offset, value);
+        } break;
+    }
+}
+
+void psx_mc1_write16(psx_mc1_t* mc1, uint32_t offset, uint16_t value) {
+    log_warn("Unhandled 16-bit MC1 write at offset %08x (%04x)", offset, value);
+}
+
+void psx_mc1_write8(psx_mc1_t* mc1, uint32_t offset, uint8_t value) {
+    log_warn("Unhandled 8-bit MC1 write at offset %08x (%02x)", offset, value);
+}
+
+uint32_t psx_mc1_get_bios_read_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_ram_read_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_dma_read_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_exp1_read_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_mc1_read_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_mc2_read_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_mc3_read_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_ic_read_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_scratchpad_read_delay(psx_mc1_t* mc1) {
+    return 1;
+}
+
+uint32_t psx_mc1_get_gpu_read_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_spu_read_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_timer_read_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_cdrom_read_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_pad_read_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_bios_write_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_ram_write_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_dma_write_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_exp1_write_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_mc1_write_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_mc2_write_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_mc3_write_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_ic_write_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_scratchpad_write_delay(psx_mc1_t* mc1) {
+    return 1;
+}
+
+uint32_t psx_mc1_get_gpu_write_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_spu_write_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_timer_write_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_cdrom_write_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+uint32_t psx_mc1_get_pad_write_delay(psx_mc1_t* mc1) {
+    return DEFAULT_DLY;
+}
+
+void psx_mc1_destroy(psx_mc1_t* mc1) {
+    free(mc1);
 }
\ No newline at end of file
--- a/psx/dev/mc1.h
+++ b/psx/dev/mc1.h
@@ -1,69 +1,69 @@
-#ifndef MC1_H
-#define MC1_H
-
-#include <stdint.h>
-
-#define PSX_MC1_BEGIN 0x1f801000
-#define PSX_MC1_SIZE  0x24
-#define PSX_MC1_END   0x1f801023
-
-typedef struct {
-    uint32_t bus_delay;
-    uint32_t io_base, io_size;
-
-    uint32_t exp1_base;
-    uint32_t exp2_base;
-    uint32_t exp1_delay;
-    uint32_t exp3_delay;
-    uint32_t bios_delay;
-    uint32_t spu_delay;
-    uint32_t cdrom_delay;
-    uint32_t exp2_delay;
-    uint32_t com_delay;
-} psx_mc1_t;
-
-typedef struct {
-    int fst;
-    int seq;
-} psx_access_delay_t;
-
-psx_mc1_t* psx_mc1_create();
-void psx_mc1_init(psx_mc1_t*);
-uint32_t psx_mc1_read32(psx_mc1_t*, uint32_t);
-uint16_t psx_mc1_read16(psx_mc1_t*, uint32_t);
-uint8_t psx_mc1_read8(psx_mc1_t*, uint32_t);
-void psx_mc1_write32(psx_mc1_t*, uint32_t, uint32_t);
-void psx_mc1_write16(psx_mc1_t*, uint32_t, uint16_t);
-void psx_mc1_write8(psx_mc1_t*, uint32_t, uint8_t);
-void psx_mc1_destroy(psx_mc1_t*);
-uint32_t psx_mc1_get_bios_read_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_ram_read_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_dma_read_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_exp1_read_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_mc1_read_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_mc2_read_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_mc3_read_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_ic_read_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_scratchpad_read_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_gpu_read_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_spu_read_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_timer_read_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_cdrom_read_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_pad_read_delay(psx_mc1_t*);
-
-uint32_t psx_mc1_get_bios_write_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_ram_write_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_dma_write_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_exp1_write_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_mc1_write_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_mc2_write_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_mc3_write_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_ic_write_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_scratchpad_write_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_gpu_write_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_spu_write_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_timer_write_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_cdrom_write_delay(psx_mc1_t*);
-uint32_t psx_mc1_get_pad_write_delay(psx_mc1_t*);
-
+#ifndef MC1_H
+#define MC1_H
+
+#include <stdint.h>
+
+#define PSX_MC1_BEGIN 0x1f801000
+#define PSX_MC1_SIZE  0x24
+#define PSX_MC1_END   0x1f801023
+
+typedef struct {
+    uint32_t bus_delay;
+    uint32_t io_base, io_size;
+
+    uint32_t exp1_base;
+    uint32_t exp2_base;
+    uint32_t exp1_delay;
+    uint32_t exp3_delay;
+    uint32_t bios_delay;
+    uint32_t spu_delay;
+    uint32_t cdrom_delay;
+    uint32_t exp2_delay;
+    uint32_t com_delay;
+} psx_mc1_t;
+
+typedef struct {
+    int fst;
+    int seq;
+} psx_access_delay_t;
+
+psx_mc1_t* psx_mc1_create();
+void psx_mc1_init(psx_mc1_t*);
+uint32_t psx_mc1_read32(psx_mc1_t*, uint32_t);
+uint16_t psx_mc1_read16(psx_mc1_t*, uint32_t);
+uint8_t psx_mc1_read8(psx_mc1_t*, uint32_t);
+void psx_mc1_write32(psx_mc1_t*, uint32_t, uint32_t);
+void psx_mc1_write16(psx_mc1_t*, uint32_t, uint16_t);
+void psx_mc1_write8(psx_mc1_t*, uint32_t, uint8_t);
+void psx_mc1_destroy(psx_mc1_t*);
+uint32_t psx_mc1_get_bios_read_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_ram_read_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_dma_read_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_exp1_read_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_mc1_read_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_mc2_read_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_mc3_read_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_ic_read_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_scratchpad_read_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_gpu_read_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_spu_read_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_timer_read_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_cdrom_read_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_pad_read_delay(psx_mc1_t*);
+
+uint32_t psx_mc1_get_bios_write_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_ram_write_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_dma_write_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_exp1_write_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_mc1_write_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_mc2_write_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_mc3_write_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_ic_write_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_scratchpad_write_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_gpu_write_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_spu_write_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_timer_write_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_cdrom_write_delay(psx_mc1_t*);
+uint32_t psx_mc1_get_pad_write_delay(psx_mc1_t*);
+
 #endif
\ No newline at end of file
--- a/psx/dev/mc2.c
+++ b/psx/dev/mc2.c
@@ -1,66 +1,66 @@
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "mc2.h"
-#include "../log.h"
-
-psx_mc2_t* psx_mc2_create() {
-    return (psx_mc2_t*)malloc(sizeof(psx_mc2_t));
-}
-
-/*
-  1F801060h 4/2  RAM_SIZE (usually 00000B88h; 2MB RAM mirrored in first 8MB)
-*/
-void psx_mc2_init(psx_mc2_t* mc2) {
-    memset(mc2, 0, sizeof(psx_mc2_t));
-
-    mc2->io_base = PSX_MC2_BEGIN;
-    mc2->io_size = PSX_MC2_SIZE;
-
-    mc2->ram_size = 0x00000b88;
-}
-
-uint32_t psx_mc2_read32(psx_mc2_t* mc2, uint32_t offset) {
-    switch (offset) {
-        case 0x00: return mc2->ram_size;
-    }
-
-    log_warn("Unhandled 32-bit MC2 read at offset %08x", offset);
-
-    return 0x0;
-}
-
-uint16_t psx_mc2_read16(psx_mc2_t* mc2, uint32_t offset) {
-    log_warn("Unhandled 16-bit MC2 read at offset %08x", offset);
-
-    return 0x0;
-}
-
-uint8_t psx_mc2_read8(psx_mc2_t* mc2, uint32_t offset) {
-    log_warn("Unhandled 8-bit MC2 read at offset %08x", offset);
-
-    return 0x0;
-}
-
-void psx_mc2_write32(psx_mc2_t* mc2, uint32_t offset, uint32_t value) {
-    switch (offset) {
-        case 0x00: mc2->ram_size = value; break;
-
-        default: {
-            log_warn("Unhandled 32-bit MC2 write at offset %08x (%08x)", offset, value);
-        } break;
-    }
-}
-
-void psx_mc2_write16(psx_mc2_t* mc2, uint32_t offset, uint16_t value) {
-    log_warn("Unhandled 16-bit MC2 write at offset %08x (%04x)", offset, value);
-}
-
-void psx_mc2_write8(psx_mc2_t* mc2, uint32_t offset, uint8_t value) {
-    log_warn("Unhandled 8-bit MC2 write at offset %08x (%02x)", offset, value);
-}
-
-void psx_mc2_destroy(psx_mc2_t* mc2) {
-    free(mc2);
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mc2.h"
+#include "../log.h"
+
+psx_mc2_t* psx_mc2_create() {
+    return (psx_mc2_t*)malloc(sizeof(psx_mc2_t));
+}
+
+/*
+  1F801060h 4/2  RAM_SIZE (usually 00000B88h; 2MB RAM mirrored in first 8MB)
+*/
+void psx_mc2_init(psx_mc2_t* mc2) {
+    memset(mc2, 0, sizeof(psx_mc2_t));
+
+    mc2->io_base = PSX_MC2_BEGIN;
+    mc2->io_size = PSX_MC2_SIZE;
+
+    mc2->ram_size = 0x00000b88;
+}
+
+uint32_t psx_mc2_read32(psx_mc2_t* mc2, uint32_t offset) {
+    switch (offset) {
+        case 0x00: return mc2->ram_size;
+    }
+
+    log_warn("Unhandled 32-bit MC2 read at offset %08x", offset);
+
+    return 0x0;
+}
+
+uint16_t psx_mc2_read16(psx_mc2_t* mc2, uint32_t offset) {
+    log_warn("Unhandled 16-bit MC2 read at offset %08x", offset);
+
+    return 0x0;
+}
+
+uint8_t psx_mc2_read8(psx_mc2_t* mc2, uint32_t offset) {
+    log_warn("Unhandled 8-bit MC2 read at offset %08x", offset);
+
+    return 0x0;
+}
+
+void psx_mc2_write32(psx_mc2_t* mc2, uint32_t offset, uint32_t value) {
+    switch (offset) {
+        case 0x00: mc2->ram_size = value; break;
+
+        default: {
+            log_warn("Unhandled 32-bit MC2 write at offset %08x (%08x)", offset, value);
+        } break;
+    }
+}
+
+void psx_mc2_write16(psx_mc2_t* mc2, uint32_t offset, uint16_t value) {
+    log_warn("Unhandled 16-bit MC2 write at offset %08x (%04x)", offset, value);
+}
+
+void psx_mc2_write8(psx_mc2_t* mc2, uint32_t offset, uint8_t value) {
+    log_warn("Unhandled 8-bit MC2 write at offset %08x (%02x)", offset, value);
+}
+
+void psx_mc2_destroy(psx_mc2_t* mc2) {
+    free(mc2);
 }
\ No newline at end of file
--- a/psx/dev/mc2.h
+++ b/psx/dev/mc2.h
@@ -1,27 +1,27 @@
-#ifndef MC2_H
-#define MC2_H
-
-#include <stdint.h>
-
-#define PSX_MC2_BEGIN 0x1f801060
-#define PSX_MC2_SIZE  0x4
-#define PSX_MC2_END   0x1F801063
-
-typedef struct {
-    uint32_t bus_delay;
-    uint32_t io_base, io_size;
-
-    uint32_t ram_size;
-} psx_mc2_t;
-
-psx_mc2_t* psx_mc2_create();
-void psx_mc2_init(psx_mc2_t*);
-uint32_t psx_mc2_read32(psx_mc2_t*, uint32_t);
-uint16_t psx_mc2_read16(psx_mc2_t*, uint32_t);
-uint8_t psx_mc2_read8(psx_mc2_t*, uint32_t);
-void psx_mc2_write32(psx_mc2_t*, uint32_t, uint32_t);
-void psx_mc2_write16(psx_mc2_t*, uint32_t, uint16_t);
-void psx_mc2_write8(psx_mc2_t*, uint32_t, uint8_t);
-void psx_mc2_destroy(psx_mc2_t*);
-
+#ifndef MC2_H
+#define MC2_H
+
+#include <stdint.h>
+
+#define PSX_MC2_BEGIN 0x1f801060
+#define PSX_MC2_SIZE  0x4
+#define PSX_MC2_END   0x1F801063
+
+typedef struct {
+    uint32_t bus_delay;
+    uint32_t io_base, io_size;
+
+    uint32_t ram_size;
+} psx_mc2_t;
+
+psx_mc2_t* psx_mc2_create();
+void psx_mc2_init(psx_mc2_t*);
+uint32_t psx_mc2_read32(psx_mc2_t*, uint32_t);
+uint16_t psx_mc2_read16(psx_mc2_t*, uint32_t);
+uint8_t psx_mc2_read8(psx_mc2_t*, uint32_t);
+void psx_mc2_write32(psx_mc2_t*, uint32_t, uint32_t);
+void psx_mc2_write16(psx_mc2_t*, uint32_t, uint16_t);
+void psx_mc2_write8(psx_mc2_t*, uint32_t, uint8_t);
+void psx_mc2_destroy(psx_mc2_t*);
+
 #endif
\ No newline at end of file
--- a/psx/dev/mc3.c
+++ b/psx/dev/mc3.c
@@ -1,64 +1,64 @@
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "mc3.h"
-#include "../log.h"
-
-psx_mc3_t* psx_mc3_create() {
-    return (psx_mc3_t*)malloc(sizeof(psx_mc3_t));
-}
-
-/*
-  FFFE0130h 4        Cache Control
-*/
-void psx_mc3_init(psx_mc3_t* mc3) {
-    memset(mc3, 0, sizeof(psx_mc3_t));
-
-    mc3->io_base = PSX_MC3_BEGIN;
-    mc3->io_size = PSX_MC3_SIZE;
-}
-
-uint32_t psx_mc3_read32(psx_mc3_t* mc3, uint32_t offset) {
-    switch (offset) {
-        case 0x00: return mc3->cache_control;
-    }
-
-    log_warn("Unhandled 32-bit MC3 read at offset %08x", offset);
-
-    return 0x0;
-}
-
-uint16_t psx_mc3_read16(psx_mc3_t* mc3, uint32_t offset) {
-    log_warn("Unhandled 16-bit MC3 read at offset %08x", offset);
-
-    return 0x0;
-}
-
-uint8_t psx_mc3_read8(psx_mc3_t* mc3, uint32_t offset) {
-    log_warn("Unhandled 8-bit MC3 read at offset %08x", offset);
-
-    return 0x0;
-}
-
-void psx_mc3_write32(psx_mc3_t* mc3, uint32_t offset, uint32_t value) {
-    switch (offset) {
-        case 0x00: mc3->cache_control = value; break;
-
-        default: {
-            log_warn("Unhandled 32-bit MC3 write at offset %08x (%08x)", offset, value);
-        } break;
-    }
-}
-
-void psx_mc3_write16(psx_mc3_t* mc3, uint32_t offset, uint16_t value) {
-    log_warn("Unhandled 16-bit MC3 write at offset %08x (%04x)", offset, value);
-}
-
-void psx_mc3_write8(psx_mc3_t* mc3, uint32_t offset, uint8_t value) {
-    log_warn("Unhandled 8-bit MC3 write at offset %08x (%02x)", offset, value);
-}
-
-void psx_mc3_destroy(psx_mc3_t* mc3) {
-    free(mc3);
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mc3.h"
+#include "../log.h"
+
+psx_mc3_t* psx_mc3_create() {
+    return (psx_mc3_t*)malloc(sizeof(psx_mc3_t));
+}
+
+/*
+  FFFE0130h 4        Cache Control
+*/
+void psx_mc3_init(psx_mc3_t* mc3) {
+    memset(mc3, 0, sizeof(psx_mc3_t));
+
+    mc3->io_base = PSX_MC3_BEGIN;
+    mc3->io_size = PSX_MC3_SIZE;
+}
+
+uint32_t psx_mc3_read32(psx_mc3_t* mc3, uint32_t offset) {
+    switch (offset) {
+        case 0x00: return mc3->cache_control;
+    }
+
+    log_warn("Unhandled 32-bit MC3 read at offset %08x", offset);
+
+    return 0x0;
+}
+
+uint16_t psx_mc3_read16(psx_mc3_t* mc3, uint32_t offset) {
+    log_warn("Unhandled 16-bit MC3 read at offset %08x", offset);
+
+    return 0x0;
+}
+
+uint8_t psx_mc3_read8(psx_mc3_t* mc3, uint32_t offset) {
+    log_warn("Unhandled 8-bit MC3 read at offset %08x", offset);
+
+    return 0x0;
+}
+
+void psx_mc3_write32(psx_mc3_t* mc3, uint32_t offset, uint32_t value) {
+    switch (offset) {
+        case 0x00: mc3->cache_control = value; break;
+
+        default: {
+            log_warn("Unhandled 32-bit MC3 write at offset %08x (%08x)", offset, value);
+        } break;
+    }
+}
+
+void psx_mc3_write16(psx_mc3_t* mc3, uint32_t offset, uint16_t value) {
+    log_warn("Unhandled 16-bit MC3 write at offset %08x (%04x)", offset, value);
+}
+
+void psx_mc3_write8(psx_mc3_t* mc3, uint32_t offset, uint8_t value) {
+    log_warn("Unhandled 8-bit MC3 write at offset %08x (%02x)", offset, value);
+}
+
+void psx_mc3_destroy(psx_mc3_t* mc3) {
+    free(mc3);
 }
\ No newline at end of file
--- a/psx/dev/mc3.h
+++ b/psx/dev/mc3.h
@@ -1,27 +1,27 @@
-#ifndef MC3_H
-#define MC3_H
-
-#include <stdint.h>
-
-#define PSX_MC3_BEGIN 0xfffe0130
-#define PSX_MC3_SIZE  0x4
-#define PSX_MC3_END   0xfffe0133
-
-typedef struct {
-    uint32_t bus_delay;
-    uint32_t io_base, io_size;
-
-    uint32_t cache_control;
-} psx_mc3_t;
-
-psx_mc3_t* psx_mc3_create();
-void psx_mc3_init(psx_mc3_t*);
-uint32_t psx_mc3_read32(psx_mc3_t*, uint32_t);
-uint16_t psx_mc3_read16(psx_mc3_t*, uint32_t);
-uint8_t psx_mc3_read8(psx_mc3_t*, uint32_t);
-void psx_mc3_write32(psx_mc3_t*, uint32_t, uint32_t);
-void psx_mc3_write16(psx_mc3_t*, uint32_t, uint16_t);
-void psx_mc3_write8(psx_mc3_t*, uint32_t, uint8_t);
-void psx_mc3_destroy(psx_mc3_t*);
-
+#ifndef MC3_H
+#define MC3_H
+
+#include <stdint.h>
+
+#define PSX_MC3_BEGIN 0xfffe0130
+#define PSX_MC3_SIZE  0x4
+#define PSX_MC3_END   0xfffe0133
+
+typedef struct {
+    uint32_t bus_delay;
+    uint32_t io_base, io_size;
+
+    uint32_t cache_control;
+} psx_mc3_t;
+
+psx_mc3_t* psx_mc3_create();
+void psx_mc3_init(psx_mc3_t*);
+uint32_t psx_mc3_read32(psx_mc3_t*, uint32_t);
+uint16_t psx_mc3_read16(psx_mc3_t*, uint32_t);
+uint8_t psx_mc3_read8(psx_mc3_t*, uint32_t);
+void psx_mc3_write32(psx_mc3_t*, uint32_t, uint32_t);
+void psx_mc3_write16(psx_mc3_t*, uint32_t, uint16_t);
+void psx_mc3_write8(psx_mc3_t*, uint32_t, uint8_t);
+void psx_mc3_destroy(psx_mc3_t*);
+
 #endif
\ No newline at end of file
--- a/psx/dev/mcd.c
+++ b/psx/dev/mcd.c
@@ -1,184 +1,184 @@
-#include "mcd.h"
-#include "../log.h"
-
-psx_mcd_t* psx_mcd_create() {
-    return (psx_mcd_t*)malloc(sizeof(psx_mcd_t));
-}
-
-void psx_mcd_init(psx_mcd_t* mcd, const char* path) {
-    memset(mcd, 0, sizeof(psx_mcd_t));
-
-    mcd->state = MCD_STATE_TX_HIZ;
-    mcd->flag = 0x08;
-    mcd->path = path;
-    mcd->buf = malloc(MCD_MEMORY_SIZE);
-
-    memset(mcd->buf, 0, MCD_MEMORY_SIZE);
-
-    if (!path)
-        return;
-
-    FILE* file = fopen(path, "rb");
-
-    if (!file)
-        return;
-
-    if (!fread(mcd->buf, 1, MCD_MEMORY_SIZE, file)) {
-        perror("Error reading memory card data");
-
-        exit(1);
-    }
-
-    fclose(file);
-}
-
-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;
-        case MCD_STATE_TX_ID1: mcd->tx_data = 0x5a; break;
-        case MCD_STATE_TX_ID2: {
-            mcd->tx_data_ready = 1;
-            mcd->tx_data = 0x5d;
-
-            switch (mcd->mode) {
-                case 'R': mcd->state = MCD_R_STATE_RX_MSB; break;
-                case 'W': mcd->state = MCD_W_STATE_RX_MSB; break;
-                case 'S': mcd->state = MCD_S_STATE_TX_ACK1; break;
-            }
-
-            // log_set_quiet(0);
-            // log_fatal("mcd read %02x", mcd->tx_data);
-            // log_set_quiet(1);
-
-            return mcd->tx_data;
-        } break;
-
-        // Read states
-        case MCD_R_STATE_RX_MSB: mcd->tx_data = 0x00; break;
-        case MCD_R_STATE_RX_LSB: mcd->tx_data = mcd->msb; break;
-        case MCD_R_STATE_TX_ACK1: mcd->tx_data = 0x5c; break;
-        case MCD_R_STATE_TX_ACK2: mcd->tx_data = 0x5d; break;
-        case MCD_R_STATE_TX_MSB: mcd->tx_data = mcd->msb; mcd->checksum  = mcd->msb; break;
-        case MCD_R_STATE_TX_LSB: mcd->tx_data = mcd->lsb; mcd->checksum ^= mcd->lsb;
-                                 mcd->pending_bytes = 128; break;
-        case MCD_R_STATE_TX_DATA: {
-            --mcd->pending_bytes;
-
-            uint8_t data = mcd->buf[mcd->addr++];
-
-            mcd->checksum ^= data;
-
-            if (!mcd->pending_bytes) {
-                mcd->tx_data = data;
-
-                break;
-            }
-
-            // log_set_quiet(0);
-            // log_fatal("mcd read %02x", data);
-            // log_set_quiet(1);
-
-            return data;
-        } break;
-        case MCD_R_STATE_TX_CHK: mcd->tx_data = mcd->checksum; break;
-        case MCD_R_STATE_TX_MEB: {
-            mcd->tx_data_ready = 0;
-            mcd->state = MCD_STATE_TX_HIZ;
-
-            // log_set_quiet(0);
-            // log_fatal("mcd read %02x", 'G');
-            // log_set_quiet(1);
-
-            return 'G';
-        } break;
-
-        /* Write states */
-        case MCD_W_STATE_RX_MSB: mcd->tx_data = 0x00; break;
-        case MCD_W_STATE_RX_LSB: mcd->tx_data = mcd->msb;
-                                 mcd->pending_bytes = 127; break;
-        case MCD_W_STATE_RX_DATA: {
-             --mcd->pending_bytes;
-
-            mcd->buf[mcd->addr++] = mcd->rx_data;
-
-            if (!mcd->pending_bytes) {
-                mcd->tx_data = mcd->rx_data;
-
-                break;
-            }
-
-            // log_set_quiet(0);
-            // log_fatal("mcd read %02x", mcd->rx_data);
-            // log_set_quiet(1);
-
-            return mcd->rx_data;
-        } break;
-        case MCD_W_STATE_RX_CHK: mcd->tx_data = mcd->rx_data; break;
-        case MCD_W_STATE_TX_ACK1: mcd->tx_data = 0x5c; break;
-        case MCD_W_STATE_TX_ACK2: mcd->tx_data = 0x5d; break;
-        case MCD_W_STATE_TX_MEB: {
-            mcd->tx_data_ready = 0;
-            mcd->state = MCD_STATE_TX_HIZ;
-
-            // log_set_quiet(0);
-            // log_fatal("mcd read %02x", 'G');
-            // log_set_quiet(1);
-
-            return 'G';
-        } break;
-    }
-
-    mcd->tx_data_ready = 1;
-    mcd->state++;
-
-    // log_set_quiet(0);
-    // log_fatal("mcd read %02x", mcd->tx_data);
-    // 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);
-
-    switch (mcd->state) {
-        case MCD_STATE_TX_FLG: mcd->mode = data; break;
-        case MCD_R_STATE_RX_MSB: mcd->msb = data; break;
-        case MCD_R_STATE_RX_LSB: {
-            mcd->lsb = data;
-            mcd->addr = ((mcd->msb << 8) | mcd->lsb) << 7;
-        } break;
-        case MCD_W_STATE_RX_MSB: mcd->msb = data; break;
-        case MCD_W_STATE_RX_LSB: {
-            mcd->lsb = data;
-            mcd->addr = ((mcd->msb << 8) | mcd->lsb) << 7;
-        } break;
-        case MCD_W_STATE_RX_DATA: mcd->rx_data = data; break;
-        case MCD_W_STATE_RX_CHK: /* Don't care */ break;
-    }
-}
-
-int psx_mcd_query(psx_mcd_t* mcd) {
-    return mcd->tx_data_ready;
-}
-
-void psx_mcd_reset(psx_mcd_t* mcd) {
-    mcd->state = MCD_STATE_TX_HIZ;
-}
-
-void psx_mcd_destroy(psx_mcd_t* mcd) {
-    FILE* file = fopen(mcd->path, "wb");
-
-    fwrite(mcd->buf, 1, MCD_MEMORY_SIZE, file);
-    fclose(file);
-
-    free(mcd->buf);
-    free(mcd);
+#include "mcd.h"
+#include "../log.h"
+
+psx_mcd_t* psx_mcd_create() {
+    return (psx_mcd_t*)malloc(sizeof(psx_mcd_t));
+}
+
+void psx_mcd_init(psx_mcd_t* mcd, const char* path) {
+    memset(mcd, 0, sizeof(psx_mcd_t));
+
+    mcd->state = MCD_STATE_TX_HIZ;
+    mcd->flag = 0x08;
+    mcd->path = path;
+    mcd->buf = malloc(MCD_MEMORY_SIZE);
+
+    memset(mcd->buf, 0, MCD_MEMORY_SIZE);
+
+    if (!path)
+        return;
+
+    FILE* file = fopen(path, "rb");
+
+    if (!file)
+        return;
+
+    if (!fread(mcd->buf, 1, MCD_MEMORY_SIZE, file)) {
+        perror("Error reading memory card data");
+
+        exit(1);
+    }
+
+    fclose(file);
+}
+
+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;
+        case MCD_STATE_TX_ID1: mcd->tx_data = 0x5a; break;
+        case MCD_STATE_TX_ID2: {
+            mcd->tx_data_ready = 1;
+            mcd->tx_data = 0x5d;
+
+            switch (mcd->mode) {
+                case 'R': mcd->state = MCD_R_STATE_RX_MSB; break;
+                case 'W': mcd->state = MCD_W_STATE_RX_MSB; break;
+                case 'S': mcd->state = MCD_S_STATE_TX_ACK1; break;
+            }
+
+            // log_set_quiet(0);
+            // log_fatal("mcd read %02x", mcd->tx_data);
+            // log_set_quiet(1);
+
+            return mcd->tx_data;
+        } break;
+
+        // Read states
+        case MCD_R_STATE_RX_MSB: mcd->tx_data = 0x00; break;
+        case MCD_R_STATE_RX_LSB: mcd->tx_data = mcd->msb; break;
+        case MCD_R_STATE_TX_ACK1: mcd->tx_data = 0x5c; break;
+        case MCD_R_STATE_TX_ACK2: mcd->tx_data = 0x5d; break;
+        case MCD_R_STATE_TX_MSB: mcd->tx_data = mcd->msb; mcd->checksum  = mcd->msb; break;
+        case MCD_R_STATE_TX_LSB: mcd->tx_data = mcd->lsb; mcd->checksum ^= mcd->lsb;
+                                 mcd->pending_bytes = 128; break;
+        case MCD_R_STATE_TX_DATA: {
+            --mcd->pending_bytes;
+
+            uint8_t data = mcd->buf[mcd->addr++];
+
+            mcd->checksum ^= data;
+
+            if (!mcd->pending_bytes) {
+                mcd->tx_data = data;
+
+                break;
+            }
+
+            // log_set_quiet(0);
+            // log_fatal("mcd read %02x", data);
+            // log_set_quiet(1);
+
+            return data;
+        } break;
+        case MCD_R_STATE_TX_CHK: mcd->tx_data = mcd->checksum; break;
+        case MCD_R_STATE_TX_MEB: {
+            mcd->tx_data_ready = 0;
+            mcd->state = MCD_STATE_TX_HIZ;
+
+            // log_set_quiet(0);
+            // log_fatal("mcd read %02x", 'G');
+            // log_set_quiet(1);
+
+            return 'G';
+        } break;
+
+        /* Write states */
+        case MCD_W_STATE_RX_MSB: mcd->tx_data = 0x00; break;
+        case MCD_W_STATE_RX_LSB: mcd->tx_data = mcd->msb;
+                                 mcd->pending_bytes = 127; break;
+        case MCD_W_STATE_RX_DATA: {
+             --mcd->pending_bytes;
+
+            mcd->buf[mcd->addr++] = mcd->rx_data;
+
+            if (!mcd->pending_bytes) {
+                mcd->tx_data = mcd->rx_data;
+
+                break;
+            }
+
+            // log_set_quiet(0);
+            // log_fatal("mcd read %02x", mcd->rx_data);
+            // log_set_quiet(1);
+
+            return mcd->rx_data;
+        } break;
+        case MCD_W_STATE_RX_CHK: mcd->tx_data = mcd->rx_data; break;
+        case MCD_W_STATE_TX_ACK1: mcd->tx_data = 0x5c; break;
+        case MCD_W_STATE_TX_ACK2: mcd->tx_data = 0x5d; break;
+        case MCD_W_STATE_TX_MEB: {
+            mcd->tx_data_ready = 0;
+            mcd->state = MCD_STATE_TX_HIZ;
+
+            // log_set_quiet(0);
+            // log_fatal("mcd read %02x", 'G');
+            // log_set_quiet(1);
+
+            return 'G';
+        } break;
+    }
+
+    mcd->tx_data_ready = 1;
+    mcd->state++;
+
+    // log_set_quiet(0);
+    // log_fatal("mcd read %02x", mcd->tx_data);
+    // 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);
+
+    switch (mcd->state) {
+        case MCD_STATE_TX_FLG: mcd->mode = data; break;
+        case MCD_R_STATE_RX_MSB: mcd->msb = data; break;
+        case MCD_R_STATE_RX_LSB: {
+            mcd->lsb = data;
+            mcd->addr = ((mcd->msb << 8) | mcd->lsb) << 7;
+        } break;
+        case MCD_W_STATE_RX_MSB: mcd->msb = data; break;
+        case MCD_W_STATE_RX_LSB: {
+            mcd->lsb = data;
+            mcd->addr = ((mcd->msb << 8) | mcd->lsb) << 7;
+        } break;
+        case MCD_W_STATE_RX_DATA: mcd->rx_data = data; break;
+        case MCD_W_STATE_RX_CHK: /* Don't care */ break;
+    }
+}
+
+int psx_mcd_query(psx_mcd_t* mcd) {
+    return mcd->tx_data_ready;
+}
+
+void psx_mcd_reset(psx_mcd_t* mcd) {
+    mcd->state = MCD_STATE_TX_HIZ;
+}
+
+void psx_mcd_destroy(psx_mcd_t* mcd) {
+    FILE* file = fopen(mcd->path, "wb");
+
+    fwrite(mcd->buf, 1, MCD_MEMORY_SIZE, file);
+    fclose(file);
+
+    free(mcd->buf);
+    free(mcd);
 }
\ No newline at end of file
--- a/psx/dev/mcd.h
+++ b/psx/dev/mcd.h
@@ -1,64 +1,64 @@
-#ifndef MCD_H
-#define MCD_H
-
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-
-#define MCD_MEMORY_SIZE 0x20000 // 128 KB
-
-enum {
-    MCD_STATE_TX_HIZ = 0,
-    MCD_STATE_TX_FLG,
-    MCD_STATE_TX_ID1,
-    MCD_STATE_TX_ID2,
-    MCD_R_STATE_RX_MSB,
-    MCD_R_STATE_RX_LSB,
-    MCD_R_STATE_TX_ACK1,
-    MCD_R_STATE_TX_ACK2,
-    MCD_R_STATE_TX_MSB,
-    MCD_R_STATE_TX_LSB,
-    MCD_R_STATE_TX_DATA,
-    MCD_R_STATE_TX_CHK,
-    MCD_R_STATE_TX_MEB,
-    MCD_W_STATE_RX_MSB,
-    MCD_W_STATE_RX_LSB,
-    MCD_W_STATE_RX_DATA,
-    MCD_W_STATE_RX_CHK,
-    MCD_W_STATE_TX_ACK1,
-    MCD_W_STATE_TX_ACK2,
-    MCD_W_STATE_TX_MEB,
-    MCD_S_STATE_TX_ACK1,
-    MCD_S_STATE_TX_ACK2,
-    MCD_S_STATE_TX_DAT0,
-    MCD_S_STATE_TX_DAT1,
-    MCD_S_STATE_TX_DAT2,
-    MCD_S_STATE_TX_DAT3
-};
-
-typedef struct {
-    const char* path;
-    uint8_t* buf;
-    uint8_t flag;
-    uint16_t msb;
-    uint16_t lsb;
-    uint16_t addr;
-    uint8_t rx_data;
-    int pending_bytes;
-    char mode;
-    int state;
-    uint8_t tx_data;
-    int tx_data_ready;
-    uint8_t checksum;
-} psx_mcd_t;
-
-psx_mcd_t* psx_mcd_create();
-void psx_mcd_init(psx_mcd_t*, const char*);
-uint8_t psx_mcd_read(psx_mcd_t*);
-void psx_mcd_write(psx_mcd_t*, uint8_t);
-int psx_mcd_query(psx_mcd_t*);
-void psx_mcd_reset(psx_mcd_t*);
-void psx_mcd_destroy(psx_mcd_t*);
-
+#ifndef MCD_H
+#define MCD_H
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#define MCD_MEMORY_SIZE 0x20000 // 128 KB
+
+enum {
+    MCD_STATE_TX_HIZ = 0,
+    MCD_STATE_TX_FLG,
+    MCD_STATE_TX_ID1,
+    MCD_STATE_TX_ID2,
+    MCD_R_STATE_RX_MSB,
+    MCD_R_STATE_RX_LSB,
+    MCD_R_STATE_TX_ACK1,
+    MCD_R_STATE_TX_ACK2,
+    MCD_R_STATE_TX_MSB,
+    MCD_R_STATE_TX_LSB,
+    MCD_R_STATE_TX_DATA,
+    MCD_R_STATE_TX_CHK,
+    MCD_R_STATE_TX_MEB,
+    MCD_W_STATE_RX_MSB,
+    MCD_W_STATE_RX_LSB,
+    MCD_W_STATE_RX_DATA,
+    MCD_W_STATE_RX_CHK,
+    MCD_W_STATE_TX_ACK1,
+    MCD_W_STATE_TX_ACK2,
+    MCD_W_STATE_TX_MEB,
+    MCD_S_STATE_TX_ACK1,
+    MCD_S_STATE_TX_ACK2,
+    MCD_S_STATE_TX_DAT0,
+    MCD_S_STATE_TX_DAT1,
+    MCD_S_STATE_TX_DAT2,
+    MCD_S_STATE_TX_DAT3
+};
+
+typedef struct {
+    const char* path;
+    uint8_t* buf;
+    uint8_t flag;
+    uint16_t msb;
+    uint16_t lsb;
+    uint16_t addr;
+    uint8_t rx_data;
+    int pending_bytes;
+    char mode;
+    int state;
+    uint8_t tx_data;
+    int tx_data_ready;
+    uint8_t checksum;
+} psx_mcd_t;
+
+psx_mcd_t* psx_mcd_create();
+void psx_mcd_init(psx_mcd_t*, const char*);
+uint8_t psx_mcd_read(psx_mcd_t*);
+void psx_mcd_write(psx_mcd_t*, uint8_t);
+int psx_mcd_query(psx_mcd_t*);
+void psx_mcd_reset(psx_mcd_t*);
+void psx_mcd_destroy(psx_mcd_t*);
+
 #endif
\ No newline at end of file
--- a/psx/dev/mdec.c
+++ b/psx/dev/mdec.c
@@ -1,469 +1,472 @@
-#include "mdec.h"
-#include "../log.h"
-
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-
-int zigzag[] = {
-    0 , 1 , 5 , 6 , 14, 15, 27, 28,
-    2 , 4 , 7 , 13, 16, 26, 29, 42,
-    3 , 8 , 12, 17, 25, 30, 41, 43,
-    9 , 11, 18, 24, 31, 40, 44, 53,
-    10, 19, 23, 32, 39, 45, 52, 54,
-    20, 22, 33, 38, 46, 51, 55, 60,
-    21, 34, 37, 47, 50, 56, 59, 61,
-    35, 36, 48, 49, 57, 58, 62, 63
-};
-
-int zagzig[] = {
-     0,  1,  8, 16,  9,  2,  3, 10,
-    17, 24, 32, 25, 18, 11,  4,  5,
-    12, 19, 26, 33, 40, 48, 41, 34,
-    27, 20, 13,  6,  7, 14, 21, 28,
-    35, 42, 49, 56, 57, 50, 43, 36, 
-    29, 22, 15, 23, 30, 37, 44, 51,
-    58, 59, 52, 45, 38, 31, 39, 46,
-    53, 60, 61, 54, 47, 55, 62, 63
-};
-
-float scalezag[] = {
-    0.125000, 0.173380, 0.173380, 0.163320, 0.240485, 0.163320, 0.146984, 0.226532,
-    0.226532, 0.146984, 0.125000, 0.203873, 0.213388, 0.203873, 0.125000, 0.098212,
-    0.173380, 0.192044, 0.192044, 0.173380, 0.098212, 0.067650, 0.136224, 0.163320,
-    0.172835, 0.163320, 0.136224, 0.067650, 0.034487, 0.093833, 0.128320, 0.146984,
-    0.146984, 0.128320, 0.093833, 0.034487, 0.047835, 0.088388, 0.115485, 0.125000,
-    0.115485, 0.088388, 0.047835, 0.045060, 0.079547, 0.098212, 0.098212, 0.079547,
-    0.045060, 0.040553, 0.067650, 0.077165, 0.067650, 0.040553, 0.034487, 0.053152, 
-    0.053152, 0.034487, 0.027097, 0.036612, 0.027097, 0.018664, 0.018664, 0.009515
-};
-
-#define EXTS10(v) (((int16_t)((v) << 6)) >> 6)
-#define CLAMP(v, l, h) ((v <= l) ? l : ((v >= h) ? h : v))
-#define MAX(a, b) (a > b ? a : b)
-
-void real_idct(int16_t* blk, int16_t* scale) {
-    int16_t buf[64];
-
-    int16_t* src = blk;
-    int16_t* dst = buf;
-
-    for (int pass = 0; pass < 2; pass++) {
-        for (int x = 0; x < 8; x++) {
-            for (int y = 0; y < 8; y++) {
-                int sum = 0;
-
-                for (int z = 0; z < 8; z++)
-                    sum += (int32_t)src[y+z*8] * ((int32_t)scale[x+z*8] / 8);
-                
-                dst[x+y*8] = (sum + 0xfff) / 0x2000;
-            }
-        }
-
-        int16_t* temp = src;
-
-        src = dst;
-        dst = temp;
-    }
-}
-
-#define IDCT_FUNC(blk, scale) real_idct(blk, scale)
-
-uint16_t* rl_decode_block(int16_t* blk, uint16_t* src, uint8_t* quant, int16_t* scale) {
-    int k = 0;
-
-    for (int i = 0; i < 64; i++)
-        blk[i] = 0;
-    
-    uint16_t n = *src;
-
-    ++src;
-
-    while (n == 0xfe00) {
-        n = *src;
-
-        ++src;
-    }
-
-    int q_scale = (n >> 10) & 0x3f;
-
-    int16_t val = EXTS10(n & 0x3ff) * quant[k];
-
-    while (k < 64) {
-        if (!q_scale)
-            val = EXTS10(n & 0x3ff) * 2;
-
-        val = CLAMP(val, -0x400, 0x3ff);
-        // val *= scalezag[k]; // For fast IDCT
-
-        if (q_scale > 0)
-            blk[zagzig[k]] = val;
-        
-        if (!q_scale)
-            blk[k] = val;
-
-        n = *src;
-
-        ++src;
-
-        k += ((n >> 10) & 0x3f) + 1;
-
-        val = (EXTS10(n & 0x3ff) * quant[k] * q_scale + 4) / 8;
-    }
-
-    IDCT_FUNC(blk, scale);
-
-    return src;
-}
-
-//   for y=0 to 7
-//     for x=0 to 7
-//       R=[Crblk+((x+xx)/2)+((y+yy)/2)*8], B=[Cbblk+((x+xx)/2)+((y+yy)/2)*8]
-//       G=(-0.3437*B)+(-0.7143*R), R=(1.402*R), B=(1.772*B)
-//       Y=[Yblk+(x)+(y)*8]
-//       R=MinMax(-128,127,(Y+R))
-//       G=MinMax(-128,127,(Y+G))
-//       B=MinMax(-128,127,(Y+B))
-//       if unsigned then BGR=BGR xor 808080h  ;aka add 128 to the R,G,B values
-//       dst[(x+xx)+(y+yy)*16]=BGR
-//     next x
-//   next y
-
-void yuv_to_rgb(psx_mdec_t* mdec, uint8_t* buf, int xx, int yy) {
-    for (int y = 0; y < 8; y++) {
-        for (int x = 0; x < 8; x++) {
-            int16_t r = mdec->crblk[((x + xx) >> 1) + ((y + yy) >> 1) * 8];
-            int16_t b = mdec->cbblk[((x + xx) >> 1) + ((y + yy) >> 1) * 8];
-            int16_t g = (-0.3437 * (float)b) + (-0.7143 * (float)r);
-
-            r = (1.402 * (float)r);
-            b = (1.772 * (float)b);
-
-            int16_t l = mdec->yblk[x + y * 8];
-
-            r = CLAMP(l + r, -128, 127);
-            g = CLAMP(l + g, -128, 127);
-            b = CLAMP(l + b, -128, 127);
-
-            if (!mdec->output_signed) {
-                r ^= 0x80;
-                g ^= 0x80;
-                b ^= 0x80;
-            }
-
-            if (mdec->output_depth == 3) {
-                uint16_t r5 = ((uint8_t)r) >> 3;
-                uint16_t g5 = ((uint8_t)g) >> 3;
-                uint16_t b5 = ((uint8_t)b) >> 3;
-
-                uint16_t rgb = (b5 << 10) | (g5 << 5) | r5;
-
-                if (mdec->output_bit15)
-                    rgb |= 0x8000;
-                
-                buf[0 + ((x + xx) + (y + yy) * 16) * 2] = rgb & 0xff;
-                buf[1 + ((x + xx) + (y + yy) * 16) * 2] = rgb >> 8;
-            } else {
-                buf[0 + ((x + xx) + (y + yy) * 16) * 3] = r & 0xff;
-                buf[1 + ((x + xx) + (y + yy) * 16) * 3] = g & 0xff;
-                buf[2 + ((x + xx) + (y + yy) * 16) * 3] = b & 0xff;
-            }
-        }
-    }
-}
-
-void mdec_nop(psx_mdec_t* mdec) { /* Do nothing */ }
-
-void mdec_decode_macroblock(psx_mdec_t* mdec) {
-    if (mdec->output_depth < 2) {
-        size_t block_size = (mdec->output_depth == 3) ? 512 : 768;
-        size_t size = block_size;
-
-        mdec->output = malloc(size);
-
-        rl_decode_block(mdec->yblk, (uint16_t*)mdec->input, mdec->y_quant_table, mdec->scale_table);
-
-        for (int i = 0; i < 64; i++) {
-            int16_t y = mdec->yblk[i] & 0xff;
-
-            if (mdec->output_depth == 1) {
-                mdec->output[i] = y;
-            } else {
-                // To-do
-                mdec->output[i] = 0;
-            }
-        }
-
-        mdec->output_words_remaining = ((mdec->output_depth == 1) ? 64 : 32) >> 2;
-        mdec->output_empty = 0;
-        mdec->output_index = 0;
-    } else {
-        uint16_t* in = (uint16_t*)mdec->input;
-
-        size_t block_size = (mdec->output_depth == 3) ? 512 : 768;
-        size_t size = block_size;
-
-        unsigned long bytes_processed = 0;
-
-        int block_count = 1;
-
-        while (bytes_processed < mdec->input_size) {
-            if (!mdec->output) {
-                mdec->output = malloc(block_count * size);
-            } else {
-                mdec->output = realloc(mdec->output, block_count * size);
-            }
-
-            in = rl_decode_block(mdec->crblk, in, mdec->uv_quant_table, mdec->scale_table);
-            in = rl_decode_block(mdec->cbblk, in, mdec->uv_quant_table, mdec->scale_table);
-            in = rl_decode_block(mdec->yblk, in, mdec->y_quant_table, mdec->scale_table);
-            yuv_to_rgb(mdec, &mdec->output[(block_count * size) - block_size], 0, 0);
-            in = rl_decode_block(mdec->yblk, in, mdec->y_quant_table, mdec->scale_table);
-            yuv_to_rgb(mdec, &mdec->output[(block_count * size) - block_size], 8, 0);
-            in = rl_decode_block(mdec->yblk, in, mdec->y_quant_table, mdec->scale_table);
-            yuv_to_rgb(mdec, &mdec->output[(block_count * size) - block_size], 0, 8);
-            in = rl_decode_block(mdec->yblk, in, mdec->y_quant_table, mdec->scale_table);
-            yuv_to_rgb(mdec, &mdec->output[(block_count * size) - block_size], 8, 8);
-
-            bytes_processed = (uintptr_t)in - (uintptr_t)mdec->input;
-
-            ++block_count;
-        }
-
-        mdec->output_words_remaining = ((block_count - 1) * block_size) >> 2;
-        mdec->output_empty = 0;
-        mdec->output_index = 0;
-
-        // log_set_quiet(0);
-        // log_fatal("Finished decoding %u-bit MDEC data input=(%04x -> %08x)",
-        //     (mdec->output_depth == 3) ? 15 : 24,
-        //     mdec->input_size,
-        //     mdec->output_words_remaining
-        // );
-        // log_set_quiet(1);
-    }
-}
-
-void mdec_set_iqtab(psx_mdec_t* mdec) {
-    memcpy(mdec->y_quant_table, mdec->input, 64);
-
-    if (mdec->recv_color)
-        memcpy(mdec->uv_quant_table, &mdec->input[16], 64);
-}
-
-void mdec_set_scale(psx_mdec_t* mdec) {
-    memcpy(mdec->scale_table, mdec->input, 128);
-}
-
-mdec_fn_t g_mdec_cmd_table[] = {
-    mdec_nop,
-    mdec_decode_macroblock,
-    mdec_set_iqtab,
-    mdec_set_scale,
-    mdec_nop,
-    mdec_nop,
-    mdec_nop,
-    mdec_nop
-};
-
-psx_mdec_t* psx_mdec_create() {
-    return (psx_mdec_t*)malloc(sizeof(psx_mdec_t));
-}
-
-void psx_mdec_init(psx_mdec_t* mdec) {
-    memset(mdec, 0, sizeof(psx_mdec_t));
-
-    mdec->io_base = PSX_MDEC_BEGIN;
-    mdec->io_size = PSX_MDEC_SIZE;
-
-    mdec->state = MDEC_RECV_CMD;
-}
-
-uint32_t psx_mdec_read32(psx_mdec_t* mdec, uint32_t offset) {
-    switch (offset) {
-        case 0: {
-            // printf("mdec data read\n");
-            // mdec->output_empty = 1;
-            // mdec->output_index = 0;
-            // mdec->output_request = 0;
-
-            // return 0xaaaaaaaa;
-
-            if (mdec->output_words_remaining) {
-                --mdec->output_words_remaining;
-
-                // log_set_quiet(0);
-                // log_fatal("output read %08x", 0);
-                // log_set_quiet(1);
-
-                return ((uint32_t*)mdec->output)[mdec->output_index++];
-            } else {
-                // printf("no read words remaining\n");
-                mdec->output_empty = 0;
-                mdec->output_index = 0;
-                mdec->output_request = 0;
-
-                return 0xaaaaaaaa;
-            }
-        } break;
-        case 4: {
-            //printf("mdec status read\n");
-            uint32_t status = 0;
-
-            status |= mdec->words_remaining;
-            status |= mdec->current_block    << 16;
-            status |= mdec->output_bit15     << 23;
-            status |= mdec->output_signed    << 24;
-            status |= mdec->output_depth     << 25;
-            status |= mdec->output_request   << 27;
-            status |= mdec->input_request    << 28;
-            status |= mdec->busy             << 29;
-            status |= mdec->input_full       << 30;
-            status |= mdec->output_empty     << 31;
-
-            return status;
-        } break;
-    }
-
-    return 0x0;
-}
-
-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) {
-    switch (offset) {
-        case 0: {
-            //printf("mdec data write\n");
-            if (mdec->words_remaining) {
-                mdec->input[mdec->input_index++] = value;
-
-                --mdec->words_remaining;
-
-                if (!mdec->words_remaining) {
-                    //printf("no words remaining\n");
-                    mdec->output_empty = 0;
-                    mdec->input_full = 1;
-                    mdec->input_request = 0;
-                    mdec->busy = 0;
-                    mdec->output_request = mdec->enable_dma1;
-
-                    g_mdec_cmd_table[mdec->cmd >> 29](mdec);
-
-                    free(mdec->input);
-                }
-
-                break;
-            }
-
-            mdec->cmd = value;
-            mdec->output_request = 0;
-            mdec->output_empty = 1;
-            mdec->output_bit15 = (value >> 25) & 1;
-            mdec->output_signed = (value >> 26) & 1;
-            mdec->output_depth = (value >> 27) & 3;
-            mdec->input_index = 0;
-            mdec->input_full = 0;
-            mdec->busy = 1;
-
-            //log_set_quiet(0);
-            switch (mdec->cmd >> 29) {
-                case MDEC_CMD_NOP: {
-                    //printf("mdec nop\n");
-                    mdec->busy = 0;
-                    mdec->words_remaining = 0;
-
-                    log_fatal("MDEC %08x: NOP", mdec->cmd);
-                } break;
-
-                case MDEC_CMD_DECODE: {
-                    //printf("mdec decode\n");
-                    mdec->words_remaining = mdec->cmd & 0xffff;
-
-                    // printf("MDEC %08x: decode macroblock %04x\n",
-                    //     mdec->cmd,
-                    //     mdec->words_remaining
-                    // );
-                } break;
-
-                case MDEC_CMD_SET_QT: {
-                    //printf("mdec setqt\n");
-                    mdec->recv_color = mdec->cmd & 1;
-                    mdec->words_remaining = mdec->recv_color ? 32 : 16;
-
-                    log_fatal("MDEC %08x: set quant tables %04x",
-                        mdec->cmd,
-                        mdec->words_remaining
-                    );
-                } break;
-
-                case MDEC_CMD_SET_ST: {
-                    //printf("mdec setst\n");
-                    mdec->words_remaining = 32;
-
-                    log_fatal("MDEC %08x: set scale table %04x",
-                        mdec->cmd,
-                        mdec->words_remaining
-                    );
-                } break;
-            }
-            // log_set_quiet(1);
-
-            if (mdec->words_remaining) {
-                mdec->input_request = 1;
-                mdec->input_size = mdec->words_remaining * sizeof(uint32_t);
-                mdec->input_full = 0;
-                mdec->input_index = 0;
-                mdec->input = malloc(mdec->input_size);
-            }
-        } break;
-
-        case 4: {
-            //printf("mdec status write\n");
-            mdec->enable_dma0 = (value & 0x40000000) != 0;
-            mdec->enable_dma1 = (value & 0x20000000) != 0;
-
-            // Reset
-            if (value & 0x80000000) {
-                // status = 80040000h
-                mdec->busy            = 0;
-                mdec->words_remaining = 0;
-                mdec->output_bit15    = 0;
-                mdec->output_signed   = 0;
-                mdec->output_depth    = 0;
-                mdec->input_request   = 0;
-                mdec->output_request  = 0;
-                mdec->busy            = 0;
-                mdec->input_full      = 0;
-                mdec->output_empty    = 1;
-                mdec->current_block   = 4;
-            }
-        } break;
-    }
-
-    log_fatal("32-bit MDEC write offset=%u, value=%08x", offset, value);
-}
-
-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);
-}
-
-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);
-}
-
-void psx_mdec_destroy(psx_mdec_t* mdec) {
-    free(mdec);
-}
-
-#undef CLAMP
+#include "mdec.h"
+#include "../log.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+int zigzag[] = {
+    0 , 1 , 5 , 6 , 14, 15, 27, 28,
+    2 , 4 , 7 , 13, 16, 26, 29, 42,
+    3 , 8 , 12, 17, 25, 30, 41, 43,
+    9 , 11, 18, 24, 31, 40, 44, 53,
+    10, 19, 23, 32, 39, 45, 52, 54,
+    20, 22, 33, 38, 46, 51, 55, 60,
+    21, 34, 37, 47, 50, 56, 59, 61,
+    35, 36, 48, 49, 57, 58, 62, 63
+};
+
+int zagzig[] = {
+     0,  1,  8, 16,  9,  2,  3, 10,
+    17, 24, 32, 25, 18, 11,  4,  5,
+    12, 19, 26, 33, 40, 48, 41, 34,
+    27, 20, 13,  6,  7, 14, 21, 28,
+    35, 42, 49, 56, 57, 50, 43, 36, 
+    29, 22, 15, 23, 30, 37, 44, 51,
+    58, 59, 52, 45, 38, 31, 39, 46,
+    53, 60, 61, 54, 47, 55, 62, 63
+};
+
+float scalezag[] = {
+    0.125000, 0.173380, 0.173380, 0.163320, 0.240485, 0.163320, 0.146984, 0.226532,
+    0.226532, 0.146984, 0.125000, 0.203873, 0.213388, 0.203873, 0.125000, 0.098212,
+    0.173380, 0.192044, 0.192044, 0.173380, 0.098212, 0.067650, 0.136224, 0.163320,
+    0.172835, 0.163320, 0.136224, 0.067650, 0.034487, 0.093833, 0.128320, 0.146984,
+    0.146984, 0.128320, 0.093833, 0.034487, 0.047835, 0.088388, 0.115485, 0.125000,
+    0.115485, 0.088388, 0.047835, 0.045060, 0.079547, 0.098212, 0.098212, 0.079547,
+    0.045060, 0.040553, 0.067650, 0.077165, 0.067650, 0.040553, 0.034487, 0.053152, 
+    0.053152, 0.034487, 0.027097, 0.036612, 0.027097, 0.018664, 0.018664, 0.009515
+};
+
+#define EXTS10(v) (((int16_t)((v) << 6)) >> 6)
+#define CLAMP(v, l, h) ((v <= l) ? l : ((v >= h) ? h : v))
+#define MAX(a, b) (a > b ? a : b)
+
+void real_idct(int16_t* blk, int16_t* scale) {
+    int16_t buf[64];
+
+    int16_t* src = blk;
+    int16_t* dst = buf;
+
+    for (int pass = 0; pass < 2; pass++) {
+        for (int x = 0; x < 8; x++) {
+            for (int y = 0; y < 8; y++) {
+                int sum = 0;
+
+                for (int z = 0; z < 8; z++)
+                    sum += (int32_t)src[y+z*8] * ((int32_t)scale[x+z*8] / 8);
+                
+                dst[x+y*8] = (sum + 0xfff) / 0x2000;
+            }
+        }
+
+        int16_t* temp = src;
+
+        src = dst;
+        dst = temp;
+    }
+}
+
+#define IDCT_FUNC(blk, scale) real_idct(blk, scale)
+
+uint16_t* rl_decode_block(int16_t* blk, uint16_t* src, uint8_t* quant, int16_t* scale) {
+    int k = 0;
+
+    for (int i = 0; i < 64; i++)
+        blk[i] = 0;
+    
+    uint16_t n = *src;
+
+    ++src;
+
+    while (n == 0xfe00) {
+        n = *src;
+
+        ++src;
+    }
+
+    int q_scale = (n >> 10) & 0x3f;
+
+    int16_t val = EXTS10(n & 0x3ff) * quant[k];
+
+    while (k < 64) {
+        if (!q_scale)
+            val = EXTS10(n & 0x3ff) * 2;
+
+        val = CLAMP(val, -0x400, 0x3ff);
+        // val *= scalezag[k]; // For fast IDCT
+
+        if (q_scale > 0)
+            blk[zagzig[k]] = val;
+        
+        if (!q_scale)
+            blk[k] = val;
+
+        n = *src;
+
+        if (k == 63)
+            break;
+
+        ++src;
+
+        k += ((n >> 10) & 0x3f) + 1;
+
+        val = (EXTS10(n & 0x3ff) * quant[k] * q_scale + 4) / 8;
+    }
+
+    IDCT_FUNC(blk, scale);
+
+    return src;
+}
+
+//   for y=0 to 7
+//     for x=0 to 7
+//       R=[Crblk+((x+xx)/2)+((y+yy)/2)*8], B=[Cbblk+((x+xx)/2)+((y+yy)/2)*8]
+//       G=(-0.3437*B)+(-0.7143*R), R=(1.402*R), B=(1.772*B)
+//       Y=[Yblk+(x)+(y)*8]
+//       R=MinMax(-128,127,(Y+R))
+//       G=MinMax(-128,127,(Y+G))
+//       B=MinMax(-128,127,(Y+B))
+//       if unsigned then BGR=BGR xor 808080h  ;aka add 128 to the R,G,B values
+//       dst[(x+xx)+(y+yy)*16]=BGR
+//     next x
+//   next y
+
+void yuv_to_rgb(psx_mdec_t* mdec, uint8_t* buf, int xx, int yy) {
+    for (int y = 0; y < 8; y++) {
+        for (int x = 0; x < 8; x++) {
+            int16_t r = mdec->crblk[((x + xx) >> 1) + ((y + yy) >> 1) * 8];
+            int16_t b = mdec->cbblk[((x + xx) >> 1) + ((y + yy) >> 1) * 8];
+            int16_t g = (-0.3437 * (float)b) + (-0.7143 * (float)r);
+
+            r = (1.402 * (float)r);
+            b = (1.772 * (float)b);
+
+            int16_t l = mdec->yblk[x + y * 8];
+
+            r = CLAMP(l + r, -128, 127);
+            g = CLAMP(l + g, -128, 127);
+            b = CLAMP(l + b, -128, 127);
+
+            if (!mdec->output_signed) {
+                r ^= 0x80;
+                g ^= 0x80;
+                b ^= 0x80;
+            }
+
+            if (mdec->output_depth == 3) {
+                uint16_t r5 = ((uint8_t)r) >> 3;
+                uint16_t g5 = ((uint8_t)g) >> 3;
+                uint16_t b5 = ((uint8_t)b) >> 3;
+
+                uint16_t rgb = (b5 << 10) | (g5 << 5) | r5;
+
+                if (mdec->output_bit15)
+                    rgb |= 0x8000;
+                
+                buf[0 + ((x + xx) + (y + yy) * 16) * 2] = rgb & 0xff;
+                buf[1 + ((x + xx) + (y + yy) * 16) * 2] = rgb >> 8;
+            } else {
+                buf[0 + ((x + xx) + (y + yy) * 16) * 3] = r & 0xff;
+                buf[1 + ((x + xx) + (y + yy) * 16) * 3] = g & 0xff;
+                buf[2 + ((x + xx) + (y + yy) * 16) * 3] = b & 0xff;
+            }
+        }
+    }
+}
+
+void mdec_nop(psx_mdec_t* mdec) { /* Do nothing */ }
+
+void mdec_decode_macroblock(psx_mdec_t* mdec) {
+    if (mdec->output_depth < 2) {
+        size_t block_size = (mdec->output_depth == 3) ? 512 : 768;
+        size_t size = block_size;
+
+        mdec->output = malloc(size);
+
+        rl_decode_block(mdec->yblk, (uint16_t*)mdec->input, mdec->y_quant_table, mdec->scale_table);
+
+        for (int i = 0; i < 64; i++) {
+            int16_t y = mdec->yblk[i] & 0xff;
+
+            if (mdec->output_depth == 1) {
+                mdec->output[i] = y;
+            } else {
+                // To-do
+                mdec->output[i] = 0;
+            }
+        }
+
+        mdec->output_words_remaining = ((mdec->output_depth == 1) ? 64 : 32) >> 2;
+        mdec->output_empty = 0;
+        mdec->output_index = 0;
+    } else {
+        uint16_t* in = (uint16_t*)mdec->input;
+
+        size_t block_size = (mdec->output_depth == 3) ? 512 : 768;
+        size_t size = block_size;
+
+        unsigned long bytes_processed = 0;
+
+        int block_count = 1;
+
+        while (bytes_processed < mdec->input_size) {
+            if (!mdec->output) {
+                mdec->output = malloc(block_count * size);
+            } else {
+                mdec->output = realloc(mdec->output, block_count * size);
+            }
+
+            in = rl_decode_block(mdec->crblk, in, mdec->uv_quant_table, mdec->scale_table);
+            in = rl_decode_block(mdec->cbblk, in, mdec->uv_quant_table, mdec->scale_table);
+            in = rl_decode_block(mdec->yblk, in, mdec->y_quant_table, mdec->scale_table);
+            yuv_to_rgb(mdec, &mdec->output[(block_count * size) - block_size], 0, 0);
+            in = rl_decode_block(mdec->yblk, in, mdec->y_quant_table, mdec->scale_table);
+            yuv_to_rgb(mdec, &mdec->output[(block_count * size) - block_size], 8, 0);
+            in = rl_decode_block(mdec->yblk, in, mdec->y_quant_table, mdec->scale_table);
+            yuv_to_rgb(mdec, &mdec->output[(block_count * size) - block_size], 0, 8);
+            in = rl_decode_block(mdec->yblk, in, mdec->y_quant_table, mdec->scale_table);
+            yuv_to_rgb(mdec, &mdec->output[(block_count * size) - block_size], 8, 8);
+
+            bytes_processed = (uintptr_t)in - (uintptr_t)mdec->input;
+
+            ++block_count;
+        }
+
+        mdec->output_words_remaining = ((block_count - 1) * block_size) >> 2;
+        mdec->output_empty = 0;
+        mdec->output_index = 0;
+
+        // log_set_quiet(0);
+        // log_fatal("Finished decoding %u-bit MDEC data input=(%04x -> %08x)",
+        //     (mdec->output_depth == 3) ? 15 : 24,
+        //     mdec->input_size,
+        //     mdec->output_words_remaining
+        // );
+        // log_set_quiet(1);
+    }
+}
+
+void mdec_set_iqtab(psx_mdec_t* mdec) {
+    memcpy(mdec->y_quant_table, mdec->input, 64);
+
+    if (mdec->recv_color)
+        memcpy(mdec->uv_quant_table, &mdec->input[16], 64);
+}
+
+void mdec_set_scale(psx_mdec_t* mdec) {
+    memcpy(mdec->scale_table, mdec->input, 128);
+}
+
+mdec_fn_t g_mdec_cmd_table[] = {
+    mdec_nop,
+    mdec_decode_macroblock,
+    mdec_set_iqtab,
+    mdec_set_scale,
+    mdec_nop,
+    mdec_nop,
+    mdec_nop,
+    mdec_nop
+};
+
+psx_mdec_t* psx_mdec_create() {
+    return (psx_mdec_t*)malloc(sizeof(psx_mdec_t));
+}
+
+void psx_mdec_init(psx_mdec_t* mdec) {
+    memset(mdec, 0, sizeof(psx_mdec_t));
+
+    mdec->io_base = PSX_MDEC_BEGIN;
+    mdec->io_size = PSX_MDEC_SIZE;
+
+    mdec->state = MDEC_RECV_CMD;
+}
+
+uint32_t psx_mdec_read32(psx_mdec_t* mdec, uint32_t offset) {
+    switch (offset) {
+        case 0: {
+            // printf("mdec data read\n");
+            // mdec->output_empty = 1;
+            // mdec->output_index = 0;
+            // mdec->output_request = 0;
+
+            // return 0xaaaaaaaa;
+
+            if (mdec->output_words_remaining) {
+                --mdec->output_words_remaining;
+
+                // log_set_quiet(0);
+                // log_fatal("output read %08x", 0);
+                // log_set_quiet(1);
+
+                return ((uint32_t*)mdec->output)[mdec->output_index++];
+            } else {
+                // printf("no read words remaining\n");
+                mdec->output_empty = 0;
+                mdec->output_index = 0;
+                mdec->output_request = 0;
+
+                return 0xaaaaaaaa;
+            }
+        } break;
+        case 4: {
+            //printf("mdec status read\n");
+            uint32_t status = 0;
+
+            status |= mdec->words_remaining;
+            status |= mdec->current_block    << 16;
+            status |= mdec->output_bit15     << 23;
+            status |= mdec->output_signed    << 24;
+            status |= mdec->output_depth     << 25;
+            status |= mdec->output_request   << 27;
+            status |= mdec->input_request    << 28;
+            status |= mdec->busy             << 29;
+            status |= mdec->input_full       << 30;
+            status |= mdec->output_empty     << 31;
+
+            return status;
+        } break;
+    }
+
+    return 0x0;
+}
+
+uint16_t psx_mdec_read16(psx_mdec_t* mdec, uint32_t offset) {
+    printf("Unhandled 16-bit MDEC read offset=%u\n", offset);
+
+    exit(1);
+}
+
+uint8_t psx_mdec_read8(psx_mdec_t* mdec, uint32_t offset) {
+    printf("Unhandled 8-bit MDEC read offset=%u\n", offset);
+
+    exit(1);
+}
+
+void psx_mdec_write32(psx_mdec_t* mdec, uint32_t offset, uint32_t value) {
+    switch (offset) {
+        case 0: {
+            //printf("mdec data write\n");
+            if (mdec->words_remaining) {
+                mdec->input[mdec->input_index++] = value;
+
+                --mdec->words_remaining;
+
+                if (!mdec->words_remaining) {
+                    //printf("no words remaining\n");
+                    mdec->output_empty = 0;
+                    mdec->input_full = 1;
+                    mdec->input_request = 0;
+                    mdec->busy = 0;
+                    mdec->output_request = mdec->enable_dma1;
+
+                    g_mdec_cmd_table[mdec->cmd >> 29](mdec);
+
+                    free(mdec->input);
+                }
+
+                break;
+            }
+
+            mdec->cmd = value;
+            mdec->output_request = 0;
+            mdec->output_empty = 1;
+            mdec->output_bit15 = (value >> 25) & 1;
+            mdec->output_signed = (value >> 26) & 1;
+            mdec->output_depth = (value >> 27) & 3;
+            mdec->input_index = 0;
+            mdec->input_full = 0;
+            mdec->busy = 1;
+
+            //log_set_quiet(0);
+            switch (mdec->cmd >> 29) {
+                case MDEC_CMD_NOP: {
+                    //printf("mdec nop\n");
+                    mdec->busy = 0;
+                    mdec->words_remaining = 0;
+
+                    log_fatal("MDEC %08x: NOP", mdec->cmd);
+                } break;
+
+                case MDEC_CMD_DECODE: {
+                    //printf("mdec decode\n");
+                    mdec->words_remaining = mdec->cmd & 0xffff;
+
+                    // printf("MDEC %08x: decode macroblock %04x\n",
+                    //     mdec->cmd,
+                    //     mdec->words_remaining
+                    // );
+                } break;
+
+                case MDEC_CMD_SET_QT: {
+                    //printf("mdec setqt\n");
+                    mdec->recv_color = mdec->cmd & 1;
+                    mdec->words_remaining = mdec->recv_color ? 32 : 16;
+
+                    log_fatal("MDEC %08x: set quant tables %04x",
+                        mdec->cmd,
+                        mdec->words_remaining
+                    );
+                } break;
+
+                case MDEC_CMD_SET_ST: {
+                    //printf("mdec setst\n");
+                    mdec->words_remaining = 32;
+
+                    log_fatal("MDEC %08x: set scale table %04x",
+                        mdec->cmd,
+                        mdec->words_remaining
+                    );
+                } break;
+            }
+            // log_set_quiet(1);
+
+            if (mdec->words_remaining) {
+                mdec->input_request = 1;
+                mdec->input_size = mdec->words_remaining * sizeof(uint32_t);
+                mdec->input_full = 0;
+                mdec->input_index = 0;
+                mdec->input = malloc(mdec->input_size);
+            }
+        } break;
+
+        case 4: {
+            //printf("mdec status write\n");
+            mdec->enable_dma0 = (value & 0x40000000) != 0;
+            mdec->enable_dma1 = (value & 0x20000000) != 0;
+
+            // Reset
+            if (value & 0x80000000) {
+                // status = 80040000h
+                mdec->busy            = 0;
+                mdec->words_remaining = 0;
+                mdec->output_bit15    = 0;
+                mdec->output_signed   = 0;
+                mdec->output_depth    = 0;
+                mdec->input_request   = 0;
+                mdec->output_request  = 0;
+                mdec->busy            = 0;
+                mdec->input_full      = 0;
+                mdec->output_empty    = 1;
+                mdec->current_block   = 4;
+            }
+        } break;
+    }
+
+    // log_fatal("32-bit MDEC write offset=%u, value=%08x", offset, value);
+}
+
+void psx_mdec_write16(psx_mdec_t* mdec, uint32_t offset, uint16_t value) {
+    printf("Unhandled 16-bit MDEC write offset=%u, value=%04x\n", offset, value);
+}
+
+void psx_mdec_write8(psx_mdec_t* mdec, uint32_t offset, uint8_t value) {
+    printf("Unhandled 8-bit MDEC write offset=%u, value=%02x\n", offset, value);
+}
+
+void psx_mdec_destroy(psx_mdec_t* mdec) {
+    free(mdec);
+}
+
+#undef CLAMP
 #undef MAX
\ No newline at end of file
--- a/psx/dev/mdec.h
+++ b/psx/dev/mdec.h
@@ -1,100 +1,100 @@
-#ifndef MDEC_H
-#define MDEC_H
-
-#include <stdint.h>
-
-#include "../log.h"
-
-#define PSX_MDEC_SIZE    0x8
-#define PSX_MDEC_BEGIN   0x1f801820
-#define PSX_MDEC_END     0x1f801827
-
-#define MDEC_SCALE_TABLE_SIZE 64
-#define MDEC_QUANT_TABLE_SIZE 64
-
-enum {
-    MDEC_RECV_CMD,
-    MDEC_RECV_BLOCK,
-    MDEC_RECV_QUANT,
-    MDEC_RECV_QUANT_COLOR,
-    MDEC_RECV_SCALE
-};
-
-/*
-  31    Data-Out Fifo Empty (0=No, 1=Empty)
-  30    Data-In Fifo Full   (0=No, 1=Full, or Last word received)
-  29    Command Busy  (0=Ready, 1=Busy receiving or processing parameters)
-  28    Data-In Request  (set when DMA0 enabled and ready to receive data)
-  27    Data-Out Request (set when DMA1 enabled and ready to send data)
-  26-25 Data Output Depth  (0=4bit, 1=8bit, 2=24bit, 3=15bit)      ;CMD.28-27
-  24    Data Output Signed (0=Unsigned, 1=Signed)                  ;CMD.26
-  23    Data Output Bit15  (0=Clear, 1=Set) (for 15bit depth only) ;CMD.25
-  22-19 Not used (seems to be always zero)
-  18-16 Current Block (0..3=Y1..Y4, 4=Cr, 5=Cb) (or for mono: always 4=Y)
-  15-0  Number of Parameter Words remaining minus 1  (FFFFh=None)  ;CMD.Bit0-15
-*/
-
-enum {
-    MDEC_CMD_NOP,
-    MDEC_CMD_DECODE,
-    MDEC_CMD_SET_QT,
-    MDEC_CMD_SET_ST
-};
-
-typedef struct {
-    uint32_t bus_delay;
-    uint32_t io_base, io_size;
-
-    uint32_t cmd;
-
-    int state;
-    int data_remaining;
-    int index;
-
-    uint32_t* input;
-    int input_index;
-    size_t input_size;
-
-    uint8_t* output;
-    int output_index;
-    uint16_t output_words_remaining;
-
-    uint16_t words_remaining;
-    int current_block;
-    int output_bit15;
-    int output_signed;
-    int output_depth;
-    int input_request;
-    int output_request;
-    int busy;
-    int input_full;
-    int output_empty;
-
-    int enable_dma0;
-    int enable_dma1;
-
-    int recv_color;
-    uint8_t uv_quant_table[MDEC_QUANT_TABLE_SIZE];
-    uint8_t y_quant_table[MDEC_QUANT_TABLE_SIZE];
-    int16_t scale_table[MDEC_SCALE_TABLE_SIZE];
-
-    int16_t yblk[64];
-    int16_t crblk[64];
-    int16_t cbblk[64];
-
-    uint32_t status;
-} psx_mdec_t;
-
-psx_mdec_t* psx_mdec_create();
-void psx_mdec_init(psx_mdec_t*);
-uint32_t psx_mdec_read32(psx_mdec_t*, uint32_t);
-uint16_t psx_mdec_read16(psx_mdec_t*, uint32_t);
-uint8_t psx_mdec_read8(psx_mdec_t*, uint32_t);
-void psx_mdec_write32(psx_mdec_t*, uint32_t, uint32_t);
-void psx_mdec_write16(psx_mdec_t*, uint32_t, uint16_t);
-void psx_mdec_write8(psx_mdec_t*, uint32_t, uint8_t);
-void psx_mdec_destroy(psx_mdec_t*);
-
-typedef void (*mdec_fn_t)(psx_mdec_t*);
-
+#ifndef MDEC_H
+#define MDEC_H
+
+#include <stdint.h>
+
+#include "../log.h"
+
+#define PSX_MDEC_SIZE    0x8
+#define PSX_MDEC_BEGIN   0x1f801820
+#define PSX_MDEC_END     0x1f801827
+
+#define MDEC_SCALE_TABLE_SIZE 64
+#define MDEC_QUANT_TABLE_SIZE 64
+
+enum {
+    MDEC_RECV_CMD,
+    MDEC_RECV_BLOCK,
+    MDEC_RECV_QUANT,
+    MDEC_RECV_QUANT_COLOR,
+    MDEC_RECV_SCALE
+};
+
+/*
+  31    Data-Out Fifo Empty (0=No, 1=Empty)
+  30    Data-In Fifo Full   (0=No, 1=Full, or Last word received)
+  29    Command Busy  (0=Ready, 1=Busy receiving or processing parameters)
+  28    Data-In Request  (set when DMA0 enabled and ready to receive data)
+  27    Data-Out Request (set when DMA1 enabled and ready to send data)
+  26-25 Data Output Depth  (0=4bit, 1=8bit, 2=24bit, 3=15bit)      ;CMD.28-27
+  24    Data Output Signed (0=Unsigned, 1=Signed)                  ;CMD.26
+  23    Data Output Bit15  (0=Clear, 1=Set) (for 15bit depth only) ;CMD.25
+  22-19 Not used (seems to be always zero)
+  18-16 Current Block (0..3=Y1..Y4, 4=Cr, 5=Cb) (or for mono: always 4=Y)
+  15-0  Number of Parameter Words remaining minus 1  (FFFFh=None)  ;CMD.Bit0-15
+*/
+
+enum {
+    MDEC_CMD_NOP,
+    MDEC_CMD_DECODE,
+    MDEC_CMD_SET_QT,
+    MDEC_CMD_SET_ST
+};
+
+typedef struct {
+    uint32_t bus_delay;
+    uint32_t io_base, io_size;
+
+    uint32_t cmd;
+
+    int state;
+    int data_remaining;
+    int index;
+
+    uint32_t* input;
+    int input_index;
+    size_t input_size;
+
+    uint8_t* output;
+    int output_index;
+    uint16_t output_words_remaining;
+
+    uint16_t words_remaining;
+    int current_block;
+    int output_bit15;
+    int output_signed;
+    int output_depth;
+    int input_request;
+    int output_request;
+    int busy;
+    int input_full;
+    int output_empty;
+
+    int enable_dma0;
+    int enable_dma1;
+
+    int recv_color;
+    uint8_t uv_quant_table[MDEC_QUANT_TABLE_SIZE];
+    uint8_t y_quant_table[MDEC_QUANT_TABLE_SIZE];
+    int16_t scale_table[MDEC_SCALE_TABLE_SIZE];
+
+    int16_t yblk[64];
+    int16_t crblk[64];
+    int16_t cbblk[64];
+
+    uint32_t status;
+} psx_mdec_t;
+
+psx_mdec_t* psx_mdec_create();
+void psx_mdec_init(psx_mdec_t*);
+uint32_t psx_mdec_read32(psx_mdec_t*, uint32_t);
+uint16_t psx_mdec_read16(psx_mdec_t*, uint32_t);
+uint8_t psx_mdec_read8(psx_mdec_t*, uint32_t);
+void psx_mdec_write32(psx_mdec_t*, uint32_t, uint32_t);
+void psx_mdec_write16(psx_mdec_t*, uint32_t, uint16_t);
+void psx_mdec_write8(psx_mdec_t*, uint32_t, uint8_t);
+void psx_mdec_destroy(psx_mdec_t*);
+
+typedef void (*mdec_fn_t)(psx_mdec_t*);
+
 #endif
\ No newline at end of file
--- a/psx/dev/old/cdrom.c
+++ b/psx/dev/old/cdrom.c
@@ -1,1593 +1,1593 @@
-#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) {
-    printf("Unimplemented CDROM command (%u)\n", 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->ongoing_read_command) {
-                cdrom->state = CD_STATE_SEND_RESP2;
-                cdrom->delayed_command = cdrom->ongoing_read_command;
-                cdrom->irq_delay = DELAY_1MS;
-            } else {
-                cdrom->delayed_command = CDL_NONE;
-                cdrom->state = CD_STATE_RECV_CMD;
-            }
-        } break;
-    }
-}
-void cdrom_cmd_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->ongoing_read_command) {
-                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 = cdrom->ongoing_read_command;
-                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->ongoing_read_command = CDL_NONE;
-                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->ongoing_read_command = 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->ongoing_read_command = CDL_NONE;
-            cdrom->cdda_playing = 0;
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_MOTORON;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, 3);
-            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_READ);
-
-            int double_speed = cdrom->mode & MODE_SPEED;
-
-            cdrom->irq_delay = DELAY_1MS * (double_speed ? 70 : 65);
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_MOTORON;
-        } break;
-
-        case CD_STATE_SEND_RESP2: {
-            SET_BITS(ifr, IFR_INT, 2);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->state = CD_STATE_RECV_CMD;
-            cdrom->delayed_command = CDL_NONE;
-        } break;
-    }
-}
-void cdrom_cmd_stop(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            cdrom->ongoing_read_command = CDL_NONE;
-            cdrom->cdda_playing = 0;
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_STOP;
-            cdrom->seek_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->ongoing_read_command = CDL_NONE;
-            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->ongoing_read_command = CDL_NONE;
-            cdrom->mode = 0;
-            cdrom->dfifo_index = 0;
-            cdrom->dfifo_full = 0;
-            cdrom->pfifo_index = 0;
-            cdrom->rfifo_index = 0;
-            cdrom->seek_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->ongoing_read_command = 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);
-
-    // 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)
-                g_psx_cdrom_command_table[cdrom->delayed_command](cdrom);
-        }
-    }
-}
-
-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);
+#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) {
+    printf("Unimplemented CDROM command (%u)\n", 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->ongoing_read_command) {
+                cdrom->state = CD_STATE_SEND_RESP2;
+                cdrom->delayed_command = cdrom->ongoing_read_command;
+                cdrom->irq_delay = DELAY_1MS;
+            } else {
+                cdrom->delayed_command = CDL_NONE;
+                cdrom->state = CD_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+void cdrom_cmd_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->ongoing_read_command) {
+                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 = cdrom->ongoing_read_command;
+                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->ongoing_read_command = CDL_NONE;
+                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->ongoing_read_command = 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->ongoing_read_command = CDL_NONE;
+            cdrom->cdda_playing = 0;
+
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->state = CD_STATE_SEND_RESP1;
+            cdrom->delayed_command = CDL_MOTORON;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            SET_BITS(ifr, IFR_INT, 3);
+            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_READ);
+
+            int double_speed = cdrom->mode & MODE_SPEED;
+
+            cdrom->irq_delay = DELAY_1MS * (double_speed ? 70 : 65);
+            cdrom->state = CD_STATE_SEND_RESP2;
+            cdrom->delayed_command = CDL_MOTORON;
+        } break;
+
+        case CD_STATE_SEND_RESP2: {
+            SET_BITS(ifr, IFR_INT, 2);
+            RESP_PUSH(GETSTAT_MOTOR);
+
+            cdrom->state = CD_STATE_RECV_CMD;
+            cdrom->delayed_command = CDL_NONE;
+        } break;
+    }
+}
+void cdrom_cmd_stop(psx_cdrom_t* cdrom) {
+    cdrom->delayed_command = CDL_NONE;
+
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            cdrom->ongoing_read_command = CDL_NONE;
+            cdrom->cdda_playing = 0;
+
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->state = CD_STATE_SEND_RESP1;
+            cdrom->delayed_command = CDL_STOP;
+            cdrom->seek_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->ongoing_read_command = CDL_NONE;
+            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->ongoing_read_command = CDL_NONE;
+            cdrom->mode = 0;
+            cdrom->dfifo_index = 0;
+            cdrom->dfifo_full = 0;
+            cdrom->pfifo_index = 0;
+            cdrom->rfifo_index = 0;
+            cdrom->seek_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->ongoing_read_command = 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);
+
+    // 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)
+                g_psx_cdrom_command_table[cdrom->delayed_command](cdrom);
+        }
+    }
+}
+
+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
@@ -1,281 +1,281 @@
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "pad.h"
-#include "../log.h"
-
-uint32_t pad_read_rx(psx_pad_t* pad) {
-    psx_input_t* joy = pad->joy_slot[(pad->ctrl >> 13) & 1];
-    psx_mcd_t* mcd = pad->mcd_slot[(pad->ctrl >> 13) & 1];
-
-    if (!pad->dest)
-        return 0xffffffff;
-
-    if (!(pad->ctrl & CTRL_JOUT) && !(pad->ctrl & CTRL_RXEN))
-        return 0xffffffff;
-
-    switch (pad->dest) {
-        case DEST_JOY: {
-            if (!joy) {
-                pad->dest = 0;
-
-                return 0xffffffff;
-            }
-
-            uint8_t data = joy->read_func(joy->udata);
-
-            if (!joy->query_fifo_func(joy->udata))
-                pad->dest = 0;
-
-            return data;
-        } break;
-
-        case DEST_MCD: {
-            if (!mcd) {
-                pad->dest = 0;
-
-                return 0xffffffff;
-            }
-
-            uint8_t data = psx_mcd_read(mcd);
-
-            if (!psx_mcd_query(mcd))
-                pad->dest = 0;
-
-            return data;
-        } break;
-    }
-
-    return 0xffffffff;
-}
-
-void pad_write_tx(psx_pad_t* pad, uint16_t data) {
-    psx_input_t* joy = pad->joy_slot[(pad->ctrl >> 13) & 1];
-    psx_mcd_t* mcd = pad->mcd_slot[(pad->ctrl >> 13) & 1];
-
-    if (!(pad->ctrl & CTRL_TXEN))
-        return;
-
-    if (!pad->dest) {
-        if ((data == DEST_JOY) || (data == DEST_MCD)) {
-            pad->dest = data;
-        
-            if (pad->ctrl & CTRL_ACIE)
-                pad->cycles_until_irq = 1500;
-        }
-    } else {
-        switch (pad->dest) {
-            case DEST_JOY: {
-                if (!joy) {
-                    pad->dest = 0;
-
-                    return;
-                }
-
-                joy->write_func(joy->udata, data);
-
-                if (!joy->query_fifo_func(joy->udata))
-                    pad->dest = 0;
-            } break;
-
-            case DEST_MCD: {
-                if (!mcd) {
-                    pad->dest = 0;
-
-                    return;
-                }
-
-                psx_mcd_write(mcd, data);
-
-                if (!psx_mcd_query(mcd))
-                    pad->dest = 0;
-            } break;
-        }
-
-        if (pad->ctrl & CTRL_ACIE)
-            pad->cycles_until_irq = 1500;
-    }
-}
-
-uint32_t pad_handle_stat_read(psx_pad_t* pad) {
-    // log_set_quiet(0);
-    // log_fatal("pad stat read");
-    // log_set_quiet(1);
-    return 0x07;
-    psx_input_t* joy = pad->joy_slot[(pad->ctrl >> 13) & 1];
-
-    if (!joy)
-        return 0x5 | pad->stat;
-
-    return 0x5 | (joy->query_fifo_func(joy->udata) << 1);
-}
-
-void pad_handle_ctrl_write(psx_pad_t* pad, uint32_t value) {
-    pad->ctrl = value;
-
-    if (!(pad->ctrl & CTRL_JOUT)) {
-        pad->dest = 0;
-        pad->ctrl &= ~CTRL_SLOT;
-
-        psx_mcd_reset(pad->mcd_slot[(pad->ctrl >> 13) & 1]);
-    }
-}
-
-psx_pad_t* psx_pad_create() {
-    return (psx_pad_t*)malloc(sizeof(psx_pad_t));
-}
-
-void psx_pad_init(psx_pad_t* pad, psx_ic_t* ic) {
-    memset(pad, 0, sizeof(psx_pad_t));
-
-    pad->ic = ic;
-
-    pad->io_base = PSX_PAD_BEGIN;
-    pad->io_size = PSX_PAD_SIZE;
-}
-
-uint32_t psx_pad_read32(psx_pad_t* pad, uint32_t offset) {
-    switch (offset) {
-        case 0: return pad_read_rx(pad);
-        case 4: return pad_handle_stat_read(pad);
-        case 8: return pad->mode;
-        case 10: return pad->ctrl;
-        case 14: return pad->baud;
-    }
-
-    log_fatal("Unhandled 32-bit PAD read at offset %08x", offset);
-
-    return 0x0;
-}
-
-uint16_t psx_pad_read16(psx_pad_t* pad, uint32_t offset) {
-    switch (offset) {
-        case 0: return pad_read_rx(pad) & 0xffff;
-        case 4: return pad_handle_stat_read(pad) & 0xffff;
-        case 8: return pad->mode;
-        case 10: return pad->ctrl & 0xffff;
-        case 14: return pad->baud;
-    }
-
-    log_fatal("Unhandled 16-bit PAD read at offset %08x", offset);
-
-    return 0x0;
-}
-
-uint8_t psx_pad_read8(psx_pad_t* pad, uint32_t offset) {
-    switch (offset) {
-        case 0: return pad_read_rx(pad) & 0xff;
-        case 4: return pad_handle_stat_read(pad) & 0xff;
-        case 8: return pad->mode & 0xff;
-        case 10: return pad->ctrl & 0xff;
-        case 14: return pad->baud & 0xff;
-    }
-
-    log_fatal("Unhandled 8-bit PAD read at offset %08x", offset);
-
-    return 0x0;
-}
-
-void psx_pad_write32(psx_pad_t* pad, uint32_t offset, uint32_t value) {
-    switch (offset) {
-        case 0: pad_write_tx(pad, value); return;
-        case 8: pad->mode = value & 0xffff; return;
-        case 10: pad_handle_ctrl_write(pad, value); return;
-        case 14: pad->baud = value & 0xffff; return;
-    }
-
-    log_fatal("Unhandled 32-bit PAD write at offset %08x (%08x)", offset, value);
-}
-
-void psx_pad_write16(psx_pad_t* pad, uint32_t offset, uint16_t value) {
-    switch (offset) {
-        case 0: pad_write_tx(pad, value); return;
-        case 8: pad->mode = value; return;
-        case 10: pad_handle_ctrl_write(pad, value); return;
-        case 14: pad->baud = value; return;
-    }
-
-    log_fatal("Unhandled 16-bit PAD write at offset %08x (%04x)", offset, value);
-}
-
-void psx_pad_write8(psx_pad_t* pad, uint32_t offset, uint8_t value) {
-    switch (offset) {
-        case 0: pad_write_tx(pad, value); return;
-        case 8: pad->mode = value; return;
-        case 10: pad_handle_ctrl_write(pad, value); return;
-        case 14: pad->baud = value; return;
-    }
-
-    log_fatal("Unhandled 8-bit PAD write at offset %08x (%02x)", offset, value);
-}
-
-void psx_pad_button_press(psx_pad_t* pad, int slot, uint16_t data) {
-    psx_input_t* selected_slot = pad->joy_slot[slot];
-
-    if (selected_slot)
-        selected_slot->on_button_press_func(selected_slot->udata, data);
-}
-
-void psx_pad_button_release(psx_pad_t* pad, int slot, uint16_t data) {
-    psx_input_t* selected_slot = pad->joy_slot[slot];
-
-    if (selected_slot)
-        selected_slot->on_button_release_func(selected_slot->udata, data);
-}
-
-void psx_pad_attach_joy(psx_pad_t* pad, int slot, psx_input_t* input) {
-    if (pad->joy_slot[slot])
-        psx_pad_detach_joy(pad, slot);
-
-    pad->joy_slot[slot] = input;
-}
-
-void psx_pad_detach_joy(psx_pad_t* pad, int slot) {
-    if (!pad->joy_slot[slot])
-        return;
-
-    psx_input_destroy(pad->joy_slot[slot]);
-
-    pad->joy_slot[slot] = NULL;
-}
-
-void psx_pad_attach_mcd(psx_pad_t* pad, int slot, const char* path) {
-    if (pad->mcd_slot[slot])
-        psx_pad_detach_mcd(pad, slot);
-
-    psx_mcd_t* mcd = psx_mcd_create();
-    psx_mcd_init(mcd, path);
-
-    pad->mcd_slot[slot] = mcd;
-}
-
-void psx_pad_detach_mcd(psx_pad_t* pad, int slot) {
-    if (!pad->mcd_slot[slot])
-        return;
-
-    psx_mcd_destroy(pad->mcd_slot[slot]);
-
-    pad->mcd_slot[slot] = NULL;
-}
-
-void psx_pad_update(psx_pad_t* pad, int cyc) {
-    if (pad->cycles_until_irq) {
-        pad->cycles_until_irq -= cyc;
-
-        if (pad->cycles_until_irq <= 0) {
-            psx_ic_irq(pad->ic, IC_JOY);
-
-            pad->cycles_until_irq = 0;
-        }
-    }
-}
-
-void psx_pad_destroy(psx_pad_t* pad) {
-    psx_pad_detach_joy(pad, 0);
-    psx_pad_detach_joy(pad, 1);
-    psx_pad_detach_mcd(pad, 0);
-    psx_pad_detach_mcd(pad, 1);
-
-    free(pad);
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "pad.h"
+#include "../log.h"
+
+uint32_t pad_read_rx(psx_pad_t* pad) {
+    psx_input_t* joy = pad->joy_slot[(pad->ctrl >> 13) & 1];
+    psx_mcd_t* mcd = pad->mcd_slot[(pad->ctrl >> 13) & 1];
+
+    if (!pad->dest)
+        return 0xffffffff;
+
+    if (!(pad->ctrl & CTRL_JOUT) && !(pad->ctrl & CTRL_RXEN))
+        return 0xffffffff;
+
+    switch (pad->dest) {
+        case DEST_JOY: {
+            if (!joy) {
+                pad->dest = 0;
+
+                return 0xffffffff;
+            }
+
+            uint8_t data = joy->read_func(joy->udata);
+
+            if (!joy->query_fifo_func(joy->udata))
+                pad->dest = 0;
+
+            return data;
+        } break;
+
+        case DEST_MCD: {
+            if (!mcd) {
+                pad->dest = 0;
+
+                return 0xffffffff;
+            }
+
+            uint8_t data = psx_mcd_read(mcd);
+
+            if (!psx_mcd_query(mcd))
+                pad->dest = 0;
+
+            return data;
+        } break;
+    }
+
+    return 0xffffffff;
+}
+
+void pad_write_tx(psx_pad_t* pad, uint16_t data) {
+    psx_input_t* joy = pad->joy_slot[(pad->ctrl >> 13) & 1];
+    psx_mcd_t* mcd = pad->mcd_slot[(pad->ctrl >> 13) & 1];
+
+    if (!(pad->ctrl & CTRL_TXEN))
+        return;
+
+    if (!pad->dest) {
+        if ((data == DEST_JOY) || (data == DEST_MCD)) {
+            pad->dest = data;
+        
+            if (pad->ctrl & CTRL_ACIE)
+                pad->cycles_until_irq = 1500;
+        }
+    } else {
+        switch (pad->dest) {
+            case DEST_JOY: {
+                if (!joy) {
+                    pad->dest = 0;
+
+                    return;
+                }
+
+                joy->write_func(joy->udata, data);
+
+                if (!joy->query_fifo_func(joy->udata))
+                    pad->dest = 0;
+            } break;
+
+            case DEST_MCD: {
+                if (!mcd) {
+                    pad->dest = 0;
+
+                    return;
+                }
+
+                psx_mcd_write(mcd, data);
+
+                if (!psx_mcd_query(mcd))
+                    pad->dest = 0;
+            } break;
+        }
+
+        if (pad->ctrl & CTRL_ACIE)
+            pad->cycles_until_irq = 1500;
+    }
+}
+
+uint32_t pad_handle_stat_read(psx_pad_t* pad) {
+    // log_set_quiet(0);
+    // log_fatal("pad stat read");
+    // log_set_quiet(1);
+    return 0x07;
+    psx_input_t* joy = pad->joy_slot[(pad->ctrl >> 13) & 1];
+
+    if (!joy)
+        return 0x5 | pad->stat;
+
+    return 0x5 | (joy->query_fifo_func(joy->udata) << 1);
+}
+
+void pad_handle_ctrl_write(psx_pad_t* pad, uint32_t value) {
+    pad->ctrl = value;
+
+    if (!(pad->ctrl & CTRL_JOUT)) {
+        pad->dest = 0;
+        pad->ctrl &= ~CTRL_SLOT;
+
+        psx_mcd_reset(pad->mcd_slot[(pad->ctrl >> 13) & 1]);
+    }
+}
+
+psx_pad_t* psx_pad_create() {
+    return (psx_pad_t*)malloc(sizeof(psx_pad_t));
+}
+
+void psx_pad_init(psx_pad_t* pad, psx_ic_t* ic) {
+    memset(pad, 0, sizeof(psx_pad_t));
+
+    pad->ic = ic;
+
+    pad->io_base = PSX_PAD_BEGIN;
+    pad->io_size = PSX_PAD_SIZE;
+}
+
+uint32_t psx_pad_read32(psx_pad_t* pad, uint32_t offset) {
+    switch (offset) {
+        case 0: return pad_read_rx(pad);
+        case 4: return pad_handle_stat_read(pad);
+        case 8: return pad->mode;
+        case 10: return pad->ctrl;
+        case 14: return pad->baud;
+    }
+
+    printf("Unhandled 32-bit PAD read at offset %08x", offset);
+
+    return 0x0;
+}
+
+uint16_t psx_pad_read16(psx_pad_t* pad, uint32_t offset) {
+    switch (offset) {
+        case 0: return pad_read_rx(pad) & 0xffff;
+        case 4: return pad_handle_stat_read(pad) & 0xffff;
+        case 8: return pad->mode;
+        case 10: return pad->ctrl & 0xffff;
+        case 14: return pad->baud;
+    }
+
+    printf("Unhandled 16-bit PAD read at offset %08x", offset);
+
+    return 0x0;
+}
+
+uint8_t psx_pad_read8(psx_pad_t* pad, uint32_t offset) {
+    switch (offset) {
+        case 0: return pad_read_rx(pad) & 0xff;
+        case 4: return pad_handle_stat_read(pad) & 0xff;
+        case 8: return pad->mode & 0xff;
+        case 10: return pad->ctrl & 0xff;
+        case 14: return pad->baud & 0xff;
+    }
+
+    printf("Unhandled 8-bit PAD read at offset %08x", offset);
+
+    return 0x0;
+}
+
+void psx_pad_write32(psx_pad_t* pad, uint32_t offset, uint32_t value) {
+    switch (offset) {
+        case 0: pad_write_tx(pad, value); return;
+        case 8: pad->mode = value & 0xffff; return;
+        case 10: pad_handle_ctrl_write(pad, value); return;
+        case 14: pad->baud = value & 0xffff; return;
+    }
+
+    printf("Unhandled 32-bit PAD write at offset %08x (%08x)", offset, value);
+}
+
+void psx_pad_write16(psx_pad_t* pad, uint32_t offset, uint16_t value) {
+    switch (offset) {
+        case 0: pad_write_tx(pad, value); return;
+        case 8: pad->mode = value; return;
+        case 10: pad_handle_ctrl_write(pad, value); return;
+        case 14: pad->baud = value; return;
+    }
+
+    printf("Unhandled 16-bit PAD write at offset %08x (%04x)", offset, value);
+}
+
+void psx_pad_write8(psx_pad_t* pad, uint32_t offset, uint8_t value) {
+    switch (offset) {
+        case 0: pad_write_tx(pad, value); return;
+        case 8: pad->mode = value; return;
+        case 10: pad_handle_ctrl_write(pad, value); return;
+        case 14: pad->baud = value; return;
+    }
+
+    printf("Unhandled 8-bit PAD write at offset %08x (%02x)", offset, value);
+}
+
+void psx_pad_button_press(psx_pad_t* pad, int slot, uint16_t data) {
+    psx_input_t* selected_slot = pad->joy_slot[slot];
+
+    if (selected_slot)
+        selected_slot->on_button_press_func(selected_slot->udata, data);
+}
+
+void psx_pad_button_release(psx_pad_t* pad, int slot, uint16_t data) {
+    psx_input_t* selected_slot = pad->joy_slot[slot];
+
+    if (selected_slot)
+        selected_slot->on_button_release_func(selected_slot->udata, data);
+}
+
+void psx_pad_attach_joy(psx_pad_t* pad, int slot, psx_input_t* input) {
+    if (pad->joy_slot[slot])
+        psx_pad_detach_joy(pad, slot);
+
+    pad->joy_slot[slot] = input;
+}
+
+void psx_pad_detach_joy(psx_pad_t* pad, int slot) {
+    if (!pad->joy_slot[slot])
+        return;
+
+    psx_input_destroy(pad->joy_slot[slot]);
+
+    pad->joy_slot[slot] = NULL;
+}
+
+void psx_pad_attach_mcd(psx_pad_t* pad, int slot, const char* path) {
+    if (pad->mcd_slot[slot])
+        psx_pad_detach_mcd(pad, slot);
+
+    psx_mcd_t* mcd = psx_mcd_create();
+    psx_mcd_init(mcd, path);
+
+    pad->mcd_slot[slot] = mcd;
+}
+
+void psx_pad_detach_mcd(psx_pad_t* pad, int slot) {
+    if (!pad->mcd_slot[slot])
+        return;
+
+    psx_mcd_destroy(pad->mcd_slot[slot]);
+
+    pad->mcd_slot[slot] = NULL;
+}
+
+void psx_pad_update(psx_pad_t* pad, int cyc) {
+    if (pad->cycles_until_irq) {
+        pad->cycles_until_irq -= cyc;
+
+        if (pad->cycles_until_irq <= 0) {
+            psx_ic_irq(pad->ic, IC_JOY);
+
+            pad->cycles_until_irq = 0;
+        }
+    }
+}
+
+void psx_pad_destroy(psx_pad_t* pad) {
+    psx_pad_detach_joy(pad, 0);
+    psx_pad_detach_joy(pad, 1);
+    psx_pad_detach_mcd(pad, 0);
+    psx_pad_detach_mcd(pad, 1);
+
+    free(pad);
 }
\ No newline at end of file
--- a/psx/dev/pad.h
+++ b/psx/dev/pad.h
@@ -1,161 +1,161 @@
-#ifndef PAD_H
-#define PAD_H
-
-#include <stdint.h>
-
-#include "ic.h"
-#include "input.h"
-#include "mcd.h"
-
-#include "../input/sda.h"
-
-#define PSX_PAD_BEGIN 0x1f801040
-#define PSX_PAD_SIZE  0x10
-#define PSX_PAD_END   0x1f80104f
-
-// Controller/Input IDs
-#define PSXI_ID_SD          0x5a41
-#define PSXI_ID_SA_PAD      0x5a73
-#define PSXI_ID_SA_STICK    0x5a53
-
-#define PSXI_SW_SDA_SELECT      0x0001
-#define PSXI_SW_SDA_L3          0x0002
-#define PSXI_SW_SDA_R3          0x0004
-#define PSXI_SW_SDA_START       0x0008
-#define PSXI_SW_SDA_PAD_UP      0x0010
-#define PSXI_SW_SDA_PAD_RIGHT   0x0020
-#define PSXI_SW_SDA_PAD_DOWN    0x0040
-#define PSXI_SW_SDA_PAD_LEFT    0x0080
-#define PSXI_SW_SDA_L2          0x0100
-#define PSXI_SW_SDA_R2          0x0200
-#define PSXI_SW_SDA_L1          0x0400
-#define PSXI_SW_SDA_R1          0x0800
-#define PSXI_SW_SDA_TRIANGLE    0x1000
-#define PSXI_SW_SDA_CIRCLE      0x2000
-#define PSXI_SW_SDA_CROSS       0x4000
-#define PSXI_SW_SDA_SQUARE      0x8000
-
-/*
-  0     TX Ready Flag 1   (1=Ready/Started)
-  1     RX FIFO Not Empty (0=Empty, 1=Not Empty)
-  2     TX Ready Flag 2   (1=Ready/Finished)
-  3     RX Parity Error   (0=No, 1=Error; Wrong Parity, when enabled)  (sticky)
-  4     Unknown (zero)    (unlike SIO, this isn't RX FIFO Overrun flag)
-  5     Unknown (zero)    (for SIO this would be RX Bad Stop Bit)
-  6     Unknown (zero)    (for SIO this would be RX Input Level AFTER Stop bit)
-  7     /ACK Input Level  (0=High, 1=Low)
-  8     Unknown (zero)    (for SIO this would be CTS Input Level)
-  9     Interrupt Request (0=None, 1=IRQ7) (See JOY_CTRL.Bit4,10-12)   (sticky)
-  10    Unknown (always zero)
-  11-31 Baudrate Timer    (21bit timer, decrementing at 33MHz)
-*/
-
-#define STAT_TXR1 0x0001
-#define STAT_RXNE 0x0002
-#define STAT_TXR2 0x0004
-#define STAT_RXPE 0x0008
-#define STAT_UNK4 0x0010
-#define STAT_UNK5 0x0020
-#define STAT_UNK6 0x0040
-#define STAT_ACKL 0x0080
-#define STAT_UNK8 0x0100
-#define STAT_IRQ7 0x0200
-
-/*
-  0     TX Enable (TXEN)  (0=Disable, 1=Enable)
-  1     /JOYn Output      (0=High, 1=Low/Select) (/JOYn as defined in Bit13)
-  2     RX Enable (RXEN)  (0=Normal, when /JOYn=Low, 1=Force Enable Once)
-  3     Unknown? (read/write-able) (for SIO, this would be TX Output Level)
-  4     Acknowledge       (0=No change, 1=Reset JOY_STAT.Bits 3,9)          (W)
-  5     Unknown? (read/write-able) (for SIO, this would be RTS Output Level)
-  6     Reset             (0=No change, 1=Reset most JOY_registers to zero) (W)
-  7     Not used             (always zero) (unlike SIO, no matter of FACTOR)
-  8-9   RX Interrupt Mode    (0..3 = IRQ when RX FIFO contains 1,2,4,8 bytes)
-  10    TX Interrupt Enable  (0=Disable, 1=Enable) ;when JOY_STAT.0-or-2 ;Ready
-  11    RX Interrupt Enable  (0=Disable, 1=Enable) ;when N bytes in RX FIFO
-  12    ACK Interrupt Enable (0=Disable, 1=Enable) ;when JOY_STAT.7  ;/ACK=LOW
-  13    Desired Slot Number  (0=/JOY1, 1=/JOY2) (set to LOW when Bit1=1)
-  14-15 Not used             (always zero)
-*/
-
-#define CTRL_TXEN 0x0001
-#define CTRL_JOUT 0x0002
-#define CTRL_RXEN 0x0004
-#define CTRL_UNK3 0x0008
-#define CTRL_ACKN 0x0010
-#define CTRL_UNK5 0x0020
-#define CTRL_REST 0x0040
-#define CTRL_NUS7 0x0080
-#define CTRL_RXIM 0x0300
-#define CTRL_TXIE 0x0400
-#define CTRL_RXIE 0x0800
-#define CTRL_ACIE 0x1000
-#define CTRL_SLOT 0x2000
-
-enum {
-    DEST_JOY = 0x01,
-    DEST_MCD = 0x81
-};
-
-enum {
-    PAD_CONTROLLER_SDA,
-    PAD_CONTROLLER_MOUSE,
-    PAD_CONTROLLER_NAMCO_VOLUME,
-    PAD_CONTROLLER_SANKYO_NASUKA,
-    PAD_WHEEL_NEGCON,
-    PAD_WHEEL_MADCATZ,
-    PAD_WHEEL_MADCATZ_MC2
-};
-
-/*
-    To-do: Design API to interface any type of controller.
-
-    Possible names:
-    - psx_im (Input Method)
-    - psx_controller
-    - psx_input
-
-    Private API should contain a way to get the ID of
-    this controller, public API should contain the following
-    functions: (WIP)
-    - _write(data)
-    - _read()
-    _ _on_button_press(id)
-    - _on_button_release(id)
-    - _on_analog_change(id)
-*/
-
-typedef struct {
-    uint32_t bus_delay;
-    uint32_t io_base, io_size;
-
-    psx_ic_t* ic;
-    psx_input_t* joy_slot[2];
-    psx_mcd_t* mcd_slot[2];
-
-    int enable_once;
-    int cycles_until_irq;
-    int cycle_counter;
-    int dest;
-
-    uint16_t mode, ctrl, baud, stat;
-} psx_pad_t;
-
-psx_pad_t* psx_pad_create();
-void psx_pad_init(psx_pad_t*, psx_ic_t*);
-uint32_t psx_pad_read32(psx_pad_t*, uint32_t);
-uint16_t psx_pad_read16(psx_pad_t*, uint32_t);
-uint8_t psx_pad_read8(psx_pad_t*, uint32_t);
-void psx_pad_write32(psx_pad_t*, uint32_t, uint32_t);
-void psx_pad_write16(psx_pad_t*, uint32_t, uint16_t);
-void psx_pad_write8(psx_pad_t*, uint32_t, uint8_t);
-void psx_pad_destroy(psx_pad_t*);
-void psx_pad_button_press(psx_pad_t*, int, uint16_t);
-void psx_pad_button_release(psx_pad_t*, int, uint16_t);
-void psx_pad_attach_joy(psx_pad_t*, int, psx_input_t*);
-void psx_pad_detach_joy(psx_pad_t*, int);
-void psx_pad_attach_mcd(psx_pad_t*, int, const char*);
-void psx_pad_detach_mcd(psx_pad_t*, int);
-void psx_pad_update(psx_pad_t*, int);
-
+#ifndef PAD_H
+#define PAD_H
+
+#include <stdint.h>
+
+#include "ic.h"
+#include "input.h"
+#include "mcd.h"
+
+#include "../input/sda.h"
+
+#define PSX_PAD_BEGIN 0x1f801040
+#define PSX_PAD_SIZE  0x10
+#define PSX_PAD_END   0x1f80104f
+
+// Controller/Input IDs
+#define PSXI_ID_SD          0x5a41
+#define PSXI_ID_SA_PAD      0x5a73
+#define PSXI_ID_SA_STICK    0x5a53
+
+#define PSXI_SW_SDA_SELECT      0x0001
+#define PSXI_SW_SDA_L3          0x0002
+#define PSXI_SW_SDA_R3          0x0004
+#define PSXI_SW_SDA_START       0x0008
+#define PSXI_SW_SDA_PAD_UP      0x0010
+#define PSXI_SW_SDA_PAD_RIGHT   0x0020
+#define PSXI_SW_SDA_PAD_DOWN    0x0040
+#define PSXI_SW_SDA_PAD_LEFT    0x0080
+#define PSXI_SW_SDA_L2          0x0100
+#define PSXI_SW_SDA_R2          0x0200
+#define PSXI_SW_SDA_L1          0x0400
+#define PSXI_SW_SDA_R1          0x0800
+#define PSXI_SW_SDA_TRIANGLE    0x1000
+#define PSXI_SW_SDA_CIRCLE      0x2000
+#define PSXI_SW_SDA_CROSS       0x4000
+#define PSXI_SW_SDA_SQUARE      0x8000
+
+/*
+  0     TX Ready Flag 1   (1=Ready/Started)
+  1     RX FIFO Not Empty (0=Empty, 1=Not Empty)
+  2     TX Ready Flag 2   (1=Ready/Finished)
+  3     RX Parity Error   (0=No, 1=Error; Wrong Parity, when enabled)  (sticky)
+  4     Unknown (zero)    (unlike SIO, this isn't RX FIFO Overrun flag)
+  5     Unknown (zero)    (for SIO this would be RX Bad Stop Bit)
+  6     Unknown (zero)    (for SIO this would be RX Input Level AFTER Stop bit)
+  7     /ACK Input Level  (0=High, 1=Low)
+  8     Unknown (zero)    (for SIO this would be CTS Input Level)
+  9     Interrupt Request (0=None, 1=IRQ7) (See JOY_CTRL.Bit4,10-12)   (sticky)
+  10    Unknown (always zero)
+  11-31 Baudrate Timer    (21bit timer, decrementing at 33MHz)
+*/
+
+#define STAT_TXR1 0x0001
+#define STAT_RXNE 0x0002
+#define STAT_TXR2 0x0004
+#define STAT_RXPE 0x0008
+#define STAT_UNK4 0x0010
+#define STAT_UNK5 0x0020
+#define STAT_UNK6 0x0040
+#define STAT_ACKL 0x0080
+#define STAT_UNK8 0x0100
+#define STAT_IRQ7 0x0200
+
+/*
+  0     TX Enable (TXEN)  (0=Disable, 1=Enable)
+  1     /JOYn Output      (0=High, 1=Low/Select) (/JOYn as defined in Bit13)
+  2     RX Enable (RXEN)  (0=Normal, when /JOYn=Low, 1=Force Enable Once)
+  3     Unknown? (read/write-able) (for SIO, this would be TX Output Level)
+  4     Acknowledge       (0=No change, 1=Reset JOY_STAT.Bits 3,9)          (W)
+  5     Unknown? (read/write-able) (for SIO, this would be RTS Output Level)
+  6     Reset             (0=No change, 1=Reset most JOY_registers to zero) (W)
+  7     Not used             (always zero) (unlike SIO, no matter of FACTOR)
+  8-9   RX Interrupt Mode    (0..3 = IRQ when RX FIFO contains 1,2,4,8 bytes)
+  10    TX Interrupt Enable  (0=Disable, 1=Enable) ;when JOY_STAT.0-or-2 ;Ready
+  11    RX Interrupt Enable  (0=Disable, 1=Enable) ;when N bytes in RX FIFO
+  12    ACK Interrupt Enable (0=Disable, 1=Enable) ;when JOY_STAT.7  ;/ACK=LOW
+  13    Desired Slot Number  (0=/JOY1, 1=/JOY2) (set to LOW when Bit1=1)
+  14-15 Not used             (always zero)
+*/
+
+#define CTRL_TXEN 0x0001
+#define CTRL_JOUT 0x0002
+#define CTRL_RXEN 0x0004
+#define CTRL_UNK3 0x0008
+#define CTRL_ACKN 0x0010
+#define CTRL_UNK5 0x0020
+#define CTRL_REST 0x0040
+#define CTRL_NUS7 0x0080
+#define CTRL_RXIM 0x0300
+#define CTRL_TXIE 0x0400
+#define CTRL_RXIE 0x0800
+#define CTRL_ACIE 0x1000
+#define CTRL_SLOT 0x2000
+
+enum {
+    DEST_JOY = 0x01,
+    DEST_MCD = 0x81
+};
+
+enum {
+    PAD_CONTROLLER_SDA,
+    PAD_CONTROLLER_MOUSE,
+    PAD_CONTROLLER_NAMCO_VOLUME,
+    PAD_CONTROLLER_SANKYO_NASUKA,
+    PAD_WHEEL_NEGCON,
+    PAD_WHEEL_MADCATZ,
+    PAD_WHEEL_MADCATZ_MC2
+};
+
+/*
+    To-do: Design API to interface any type of controller.
+
+    Possible names:
+    - psx_im (Input Method)
+    - psx_controller
+    - psx_input
+
+    Private API should contain a way to get the ID of
+    this controller, public API should contain the following
+    functions: (WIP)
+    - _write(data)
+    - _read()
+    _ _on_button_press(id)
+    - _on_button_release(id)
+    - _on_analog_change(id)
+*/
+
+typedef struct {
+    uint32_t bus_delay;
+    uint32_t io_base, io_size;
+
+    psx_ic_t* ic;
+    psx_input_t* joy_slot[2];
+    psx_mcd_t* mcd_slot[2];
+
+    int enable_once;
+    int cycles_until_irq;
+    int cycle_counter;
+    int dest;
+
+    uint16_t mode, ctrl, baud, stat;
+} psx_pad_t;
+
+psx_pad_t* psx_pad_create();
+void psx_pad_init(psx_pad_t*, psx_ic_t*);
+uint32_t psx_pad_read32(psx_pad_t*, uint32_t);
+uint16_t psx_pad_read16(psx_pad_t*, uint32_t);
+uint8_t psx_pad_read8(psx_pad_t*, uint32_t);
+void psx_pad_write32(psx_pad_t*, uint32_t, uint32_t);
+void psx_pad_write16(psx_pad_t*, uint32_t, uint16_t);
+void psx_pad_write8(psx_pad_t*, uint32_t, uint8_t);
+void psx_pad_destroy(psx_pad_t*);
+void psx_pad_button_press(psx_pad_t*, int, uint16_t);
+void psx_pad_button_release(psx_pad_t*, int, uint16_t);
+void psx_pad_attach_joy(psx_pad_t*, int, psx_input_t*);
+void psx_pad_detach_joy(psx_pad_t*, int);
+void psx_pad_attach_mcd(psx_pad_t*, int, const char*);
+void psx_pad_detach_mcd(psx_pad_t*, int);
+void psx_pad_update(psx_pad_t*, int);
+
 #endif
\ No newline at end of file
--- a/psx/dev/ram.c
+++ b/psx/dev/ram.c
@@ -1,63 +1,63 @@
-#include "ram.h"
-#include "../log.h"
-
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-
-psx_ram_t* psx_ram_create() {
-    return (psx_ram_t*)malloc(sizeof(psx_ram_t));
-}
-
-void psx_ram_init(psx_ram_t* ram, psx_mc2_t* mc2) {
-    memset(ram, 0, sizeof(psx_ram_t));
-
-    ram->io_base = PSX_RAM_BEGIN;
-    ram->io_size = PSX_RAM_SIZE;
-
-    ram->mc2 = mc2;
-    ram->buf = (uint8_t*)malloc(PSX_RAM_SIZE);
-
-    memset(ram->buf, 0xee, PSX_RAM_SIZE);
-}
-
-uint32_t psx_ram_read32(psx_ram_t* ram, uint32_t offset) {
-    offset &= 0x1fffff;
-
-    return *((uint32_t*)(ram->buf + offset));
-}
-
-uint16_t psx_ram_read16(psx_ram_t* ram, uint32_t offset) {
-    offset &= 0x1fffff;
-
-    return *((uint16_t*)(ram->buf + offset));
-}
-
-uint8_t psx_ram_read8(psx_ram_t* ram, uint32_t offset) {
-    offset &= 0x1fffff;
-
-    return ram->buf[offset];
-}
-
-void psx_ram_write32(psx_ram_t* ram, uint32_t offset, uint32_t value) {
-    offset &= 0x1fffff;
-
-    *((uint32_t*)(ram->buf + offset)) = value;
-}
-
-void psx_ram_write16(psx_ram_t* ram, uint32_t offset, uint16_t value) {
-    offset &= 0x1fffff;
-
-    *((uint16_t*)(ram->buf + offset)) = value;
-}
-
-void psx_ram_write8(psx_ram_t* ram, uint32_t offset, uint8_t value) {
-    offset &= 0x1fffff;
-
-    ram->buf[offset] = value;
-}
-
-void psx_ram_destroy(psx_ram_t* ram) {
-    free(ram->buf);
-    free(ram);
+#include "ram.h"
+#include "../log.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+psx_ram_t* psx_ram_create() {
+    return (psx_ram_t*)malloc(sizeof(psx_ram_t));
+}
+
+void psx_ram_init(psx_ram_t* ram, psx_mc2_t* mc2) {
+    memset(ram, 0, sizeof(psx_ram_t));
+
+    ram->io_base = PSX_RAM_BEGIN;
+    ram->io_size = PSX_RAM_SIZE;
+
+    ram->mc2 = mc2;
+    ram->buf = (uint8_t*)malloc(PSX_RAM_SIZE);
+
+    memset(ram->buf, 0xee, PSX_RAM_SIZE);
+}
+
+uint32_t psx_ram_read32(psx_ram_t* ram, uint32_t offset) {
+    offset &= 0x1fffff;
+
+    return *((uint32_t*)(ram->buf + offset));
+}
+
+uint16_t psx_ram_read16(psx_ram_t* ram, uint32_t offset) {
+    offset &= 0x1fffff;
+
+    return *((uint16_t*)(ram->buf + offset));
+}
+
+uint8_t psx_ram_read8(psx_ram_t* ram, uint32_t offset) {
+    offset &= 0x1fffff;
+
+    return ram->buf[offset];
+}
+
+void psx_ram_write32(psx_ram_t* ram, uint32_t offset, uint32_t value) {
+    offset &= 0x1fffff;
+
+    *((uint32_t*)(ram->buf + offset)) = value;
+}
+
+void psx_ram_write16(psx_ram_t* ram, uint32_t offset, uint16_t value) {
+    offset &= 0x1fffff;
+
+    *((uint16_t*)(ram->buf + offset)) = value;
+}
+
+void psx_ram_write8(psx_ram_t* ram, uint32_t offset, uint8_t value) {
+    offset &= 0x1fffff;
+
+    ram->buf[offset] = value;
+}
+
+void psx_ram_destroy(psx_ram_t* ram) {
+    free(ram->buf);
+    free(ram);
 }
\ No newline at end of file
--- a/psx/dev/ram.h
+++ b/psx/dev/ram.h
@@ -1,34 +1,34 @@
-#ifndef RAM_H
-#define RAM_H
-
-#include <stdint.h>
-
-#include "../log.h"
-#include "mc2.h"
-
-//#define PSX_RAM_SIZE    0x200000
-#define PSX_RAM_SIZE    0x1f000000
-#define PSX_RAM_BEGIN   0x00000000
-//#define PSX_RAM_END     0x001fffff
-#define PSX_RAM_END     0x1effffff
-
-typedef struct {
-    uint32_t bus_delay;
-    uint32_t io_base, io_size;
-
-    psx_mc2_t* mc2;
-
-    uint8_t* buf;
-} psx_ram_t;
-
-psx_ram_t* psx_ram_create();
-void psx_ram_init(psx_ram_t*, psx_mc2_t*);
-uint32_t psx_ram_read32(psx_ram_t*, uint32_t);
-uint16_t psx_ram_read16(psx_ram_t*, uint32_t);
-uint8_t psx_ram_read8(psx_ram_t*, uint32_t);
-void psx_ram_write32(psx_ram_t*, uint32_t, uint32_t);
-void psx_ram_write16(psx_ram_t*, uint32_t, uint16_t);
-void psx_ram_write8(psx_ram_t*, uint32_t, uint8_t);
-void psx_ram_destroy(psx_ram_t*);
-
+#ifndef RAM_H
+#define RAM_H
+
+#include <stdint.h>
+
+#include "../log.h"
+#include "mc2.h"
+
+//#define PSX_RAM_SIZE    0x200000
+#define PSX_RAM_SIZE    0x1f000000
+#define PSX_RAM_BEGIN   0x00000000
+//#define PSX_RAM_END     0x001fffff
+#define PSX_RAM_END     0x1effffff
+
+typedef struct {
+    uint32_t bus_delay;
+    uint32_t io_base, io_size;
+
+    psx_mc2_t* mc2;
+
+    uint8_t* buf;
+} psx_ram_t;
+
+psx_ram_t* psx_ram_create();
+void psx_ram_init(psx_ram_t*, psx_mc2_t*);
+uint32_t psx_ram_read32(psx_ram_t*, uint32_t);
+uint16_t psx_ram_read16(psx_ram_t*, uint32_t);
+uint8_t psx_ram_read8(psx_ram_t*, uint32_t);
+void psx_ram_write32(psx_ram_t*, uint32_t, uint32_t);
+void psx_ram_write16(psx_ram_t*, uint32_t, uint16_t);
+void psx_ram_write8(psx_ram_t*, uint32_t, uint8_t);
+void psx_ram_destroy(psx_ram_t*);
+
 #endif
\ No newline at end of file
--- a/psx/dev/rework/gpu.c
+++ b/psx/dev/rework/gpu.c
@@ -1,1773 +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);
+#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/scratchpad.c
+++ b/psx/dev/scratchpad.c
@@ -1,48 +1,48 @@
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "../log.h"
-#include "scratchpad.h"
-
-psx_scratchpad_t* psx_scratchpad_create() {
-    return (psx_scratchpad_t*)malloc(sizeof(psx_scratchpad_t));
-}
-
-void psx_scratchpad_init(psx_scratchpad_t* scratchpad) {
-    memset(scratchpad, 0, sizeof(psx_scratchpad_t));
-
-    scratchpad->io_base = PSX_SCRATCHPAD_BEGIN;
-    scratchpad->io_size = PSX_SCRATCHPAD_SIZE;
-
-    scratchpad->buf = (uint8_t*)malloc(PSX_SCRATCHPAD_SIZE);
-}
-
-uint32_t psx_scratchpad_read32(psx_scratchpad_t* scratchpad, uint32_t offset) {
-    return *((uint32_t*)(scratchpad->buf + offset));
-}
-
-uint16_t psx_scratchpad_read16(psx_scratchpad_t* scratchpad, uint32_t offset) {
-    return *((uint16_t*)(scratchpad->buf + offset));
-}
-
-uint8_t psx_scratchpad_read8(psx_scratchpad_t* scratchpad, uint32_t offset) {
-    return scratchpad->buf[offset];
-}
-
-void psx_scratchpad_write32(psx_scratchpad_t* scratchpad, uint32_t offset, uint32_t value) {
-    *((uint32_t*)(scratchpad->buf + offset)) = value;
-}
-
-void psx_scratchpad_write16(psx_scratchpad_t* scratchpad, uint32_t offset, uint16_t value) {
-    *((uint16_t*)(scratchpad->buf + offset)) = value;
-}
-
-void psx_scratchpad_write8(psx_scratchpad_t* scratchpad, uint32_t offset, uint8_t value) {
-    scratchpad->buf[offset] = value;
-}
-
-void psx_scratchpad_destroy(psx_scratchpad_t* scratchpad) {
-    free(scratchpad->buf);
-    free(scratchpad);
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../log.h"
+#include "scratchpad.h"
+
+psx_scratchpad_t* psx_scratchpad_create() {
+    return (psx_scratchpad_t*)malloc(sizeof(psx_scratchpad_t));
+}
+
+void psx_scratchpad_init(psx_scratchpad_t* scratchpad) {
+    memset(scratchpad, 0, sizeof(psx_scratchpad_t));
+
+    scratchpad->io_base = PSX_SCRATCHPAD_BEGIN;
+    scratchpad->io_size = PSX_SCRATCHPAD_SIZE;
+
+    scratchpad->buf = (uint8_t*)malloc(PSX_SCRATCHPAD_SIZE);
+}
+
+uint32_t psx_scratchpad_read32(psx_scratchpad_t* scratchpad, uint32_t offset) {
+    return *((uint32_t*)(scratchpad->buf + offset));
+}
+
+uint16_t psx_scratchpad_read16(psx_scratchpad_t* scratchpad, uint32_t offset) {
+    return *((uint16_t*)(scratchpad->buf + offset));
+}
+
+uint8_t psx_scratchpad_read8(psx_scratchpad_t* scratchpad, uint32_t offset) {
+    return scratchpad->buf[offset];
+}
+
+void psx_scratchpad_write32(psx_scratchpad_t* scratchpad, uint32_t offset, uint32_t value) {
+    *((uint32_t*)(scratchpad->buf + offset)) = value;
+}
+
+void psx_scratchpad_write16(psx_scratchpad_t* scratchpad, uint32_t offset, uint16_t value) {
+    *((uint16_t*)(scratchpad->buf + offset)) = value;
+}
+
+void psx_scratchpad_write8(psx_scratchpad_t* scratchpad, uint32_t offset, uint8_t value) {
+    scratchpad->buf[offset] = value;
+}
+
+void psx_scratchpad_destroy(psx_scratchpad_t* scratchpad) {
+    free(scratchpad->buf);
+    free(scratchpad);
 }
\ No newline at end of file
--- a/psx/dev/scratchpad.h
+++ b/psx/dev/scratchpad.h
@@ -1,29 +1,29 @@
-#ifndef SCRATCHPAD_H
-#define SCRATCHPAD_H
-
-#include <stdint.h>
-
-#include "mc1.h"
-
-#define PSX_SCRATCHPAD_BEGIN 0x1f800000
-#define PSX_SCRATCHPAD_SIZE  0x400
-#define PSX_SCRATCHPAD_END   0x1f8003ff
-
-typedef struct {
-    uint32_t bus_delay;
-    uint32_t io_base, io_size;
-
-    uint8_t* buf;
-} psx_scratchpad_t;
-
-psx_scratchpad_t* psx_scratchpad_create();
-void psx_scratchpad_init(psx_scratchpad_t*);
-uint32_t psx_scratchpad_read32(psx_scratchpad_t*, uint32_t);
-uint16_t psx_scratchpad_read16(psx_scratchpad_t*, uint32_t);
-uint8_t psx_scratchpad_read8(psx_scratchpad_t*, uint32_t);
-void psx_scratchpad_write32(psx_scratchpad_t*, uint32_t, uint32_t);
-void psx_scratchpad_write16(psx_scratchpad_t*, uint32_t, uint16_t);
-void psx_scratchpad_write8(psx_scratchpad_t*, uint32_t, uint8_t);
-void psx_scratchpad_destroy(psx_scratchpad_t*);
-
+#ifndef SCRATCHPAD_H
+#define SCRATCHPAD_H
+
+#include <stdint.h>
+
+#include "mc1.h"
+
+#define PSX_SCRATCHPAD_BEGIN 0x1f800000
+#define PSX_SCRATCHPAD_SIZE  0x400
+#define PSX_SCRATCHPAD_END   0x1f8003ff
+
+typedef struct {
+    uint32_t bus_delay;
+    uint32_t io_base, io_size;
+
+    uint8_t* buf;
+} psx_scratchpad_t;
+
+psx_scratchpad_t* psx_scratchpad_create();
+void psx_scratchpad_init(psx_scratchpad_t*);
+uint32_t psx_scratchpad_read32(psx_scratchpad_t*, uint32_t);
+uint16_t psx_scratchpad_read16(psx_scratchpad_t*, uint32_t);
+uint8_t psx_scratchpad_read8(psx_scratchpad_t*, uint32_t);
+void psx_scratchpad_write32(psx_scratchpad_t*, uint32_t, uint32_t);
+void psx_scratchpad_write16(psx_scratchpad_t*, uint32_t, uint16_t);
+void psx_scratchpad_write8(psx_scratchpad_t*, uint32_t, uint8_t);
+void psx_scratchpad_destroy(psx_scratchpad_t*);
+
 #endif
\ No newline at end of file
--- a/psx/dev/spu.c
+++ b/psx/dev/spu.c
@@ -1,656 +1,656 @@
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "spu.h"
-#include "../log.h"
-
-#define CLAMP(v, l, h) ((v <= l) ? l : ((v >= h) ? h : v))
-#define MAX(a, b) ((a) > (b) ? (a) : (b))
-
-#define VOICE_COUNT 24
-
-static 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
-};
-
-static const int16_t g_spu_gauss_table[] = {
-    -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001,
-    -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001,
-    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001,
-    0x0001, 0x0001, 0x0001, 0x0002, 0x0002, 0x0002, 0x0003, 0x0003,
-    0x0003, 0x0004, 0x0004, 0x0005, 0x0005, 0x0006, 0x0007, 0x0007,
-    0x0008, 0x0009, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E,
-    0x000F, 0x0010, 0x0011, 0x0012, 0x0013, 0x0015, 0x0016, 0x0018,
-    0x0019, 0x001B, 0x001C, 0x001E, 0x0020, 0x0021, 0x0023, 0x0025,
-    0x0027, 0x0029, 0x002C, 0x002E, 0x0030, 0x0033, 0x0035, 0x0038,
-    0x003A, 0x003D, 0x0040, 0x0043, 0x0046, 0x0049, 0x004D, 0x0050,
-    0x0054, 0x0057, 0x005B, 0x005F, 0x0063, 0x0067, 0x006B, 0x006F,
-    0x0074, 0x0078, 0x007D, 0x0082, 0x0087, 0x008C, 0x0091, 0x0096,
-    0x009C, 0x00A1, 0x00A7, 0x00AD, 0x00B3, 0x00BA, 0x00C0, 0x00C7,
-    0x00CD, 0x00D4, 0x00DB, 0x00E3, 0x00EA, 0x00F2, 0x00FA, 0x0101,
-    0x010A, 0x0112, 0x011B, 0x0123, 0x012C, 0x0135, 0x013F, 0x0148,
-    0x0152, 0x015C, 0x0166, 0x0171, 0x017B, 0x0186, 0x0191, 0x019C,
-    0x01A8, 0x01B4, 0x01C0, 0x01CC, 0x01D9, 0x01E5, 0x01F2, 0x0200,
-    0x020D, 0x021B, 0x0229, 0x0237, 0x0246, 0x0255, 0x0264, 0x0273,
-    0x0283, 0x0293, 0x02A3, 0x02B4, 0x02C4, 0x02D6, 0x02E7, 0x02F9,
-    0x030B, 0x031D, 0x0330, 0x0343, 0x0356, 0x036A, 0x037E, 0x0392,
-    0x03A7, 0x03BC, 0x03D1, 0x03E7, 0x03FC, 0x0413, 0x042A, 0x0441,
-    0x0458, 0x0470, 0x0488, 0x04A0, 0x04B9, 0x04D2, 0x04EC, 0x0506,
-    0x0520, 0x053B, 0x0556, 0x0572, 0x058E, 0x05AA, 0x05C7, 0x05E4,
-    0x0601, 0x061F, 0x063E, 0x065C, 0x067C, 0x069B, 0x06BB, 0x06DC,
-    0x06FD, 0x071E, 0x0740, 0x0762, 0x0784, 0x07A7, 0x07CB, 0x07EF,
-    0x0813, 0x0838, 0x085D, 0x0883, 0x08A9, 0x08D0, 0x08F7, 0x091E,
-    0x0946, 0x096F, 0x0998, 0x09C1, 0x09EB, 0x0A16, 0x0A40, 0x0A6C,
-    0x0A98, 0x0AC4, 0x0AF1, 0x0B1E, 0x0B4C, 0x0B7A, 0x0BA9, 0x0BD8,
-    0x0C07, 0x0C38, 0x0C68, 0x0C99, 0x0CCB, 0x0CFD, 0x0D30, 0x0D63,
-    0x0D97, 0x0DCB, 0x0E00, 0x0E35, 0x0E6B, 0x0EA1, 0x0ED7, 0x0F0F,
-    0x0F46, 0x0F7F, 0x0FB7, 0x0FF1, 0x102A, 0x1065, 0x109F, 0x10DB,
-    0x1116, 0x1153, 0x118F, 0x11CD, 0x120B, 0x1249, 0x1288, 0x12C7,
-    0x1307, 0x1347, 0x1388, 0x13C9, 0x140B, 0x144D, 0x1490, 0x14D4,
-    0x1517, 0x155C, 0x15A0, 0x15E6, 0x162C, 0x1672, 0x16B9, 0x1700,
-    0x1747, 0x1790, 0x17D8, 0x1821, 0x186B, 0x18B5, 0x1900, 0x194B,
-    0x1996, 0x19E2, 0x1A2E, 0x1A7B, 0x1AC8, 0x1B16, 0x1B64, 0x1BB3,
-    0x1C02, 0x1C51, 0x1CA1, 0x1CF1, 0x1D42, 0x1D93, 0x1DE5, 0x1E37,
-    0x1E89, 0x1EDC, 0x1F2F, 0x1F82, 0x1FD6, 0x202A, 0x207F, 0x20D4,
-    0x2129, 0x217F, 0x21D5, 0x222C, 0x2282, 0x22DA, 0x2331, 0x2389,
-    0x23E1, 0x2439, 0x2492, 0x24EB, 0x2545, 0x259E, 0x25F8, 0x2653,
-    0x26AD, 0x2708, 0x2763, 0x27BE, 0x281A, 0x2876, 0x28D2, 0x292E,
-    0x298B, 0x29E7, 0x2A44, 0x2AA1, 0x2AFF, 0x2B5C, 0x2BBA, 0x2C18,
-    0x2C76, 0x2CD4, 0x2D33, 0x2D91, 0x2DF0, 0x2E4F, 0x2EAE, 0x2F0D,
-    0x2F6C, 0x2FCC, 0x302B, 0x308B, 0x30EA, 0x314A, 0x31AA, 0x3209,
-    0x3269, 0x32C9, 0x3329, 0x3389, 0x33E9, 0x3449, 0x34A9, 0x3509,
-    0x3569, 0x35C9, 0x3629, 0x3689, 0x36E8, 0x3748, 0x37A8, 0x3807,
-    0x3867, 0x38C6, 0x3926, 0x3985, 0x39E4, 0x3A43, 0x3AA2, 0x3B00,
-    0x3B5F, 0x3BBD, 0x3C1B, 0x3C79, 0x3CD7, 0x3D35, 0x3D92, 0x3DEF,
-    0x3E4C, 0x3EA9, 0x3F05, 0x3F62, 0x3FBD, 0x4019, 0x4074, 0x40D0,
-    0x412A, 0x4185, 0x41DF, 0x4239, 0x4292, 0x42EB, 0x4344, 0x439C,
-    0x43F4, 0x444C, 0x44A3, 0x44FA, 0x4550, 0x45A6, 0x45FC, 0x4651,
-    0x46A6, 0x46FA, 0x474E, 0x47A1, 0x47F4, 0x4846, 0x4898, 0x48E9,
-    0x493A, 0x498A, 0x49D9, 0x4A29, 0x4A77, 0x4AC5, 0x4B13, 0x4B5F,
-    0x4BAC, 0x4BF7, 0x4C42, 0x4C8D, 0x4CD7, 0x4D20, 0x4D68, 0x4DB0,
-    0x4DF7, 0x4E3E, 0x4E84, 0x4EC9, 0x4F0E, 0x4F52, 0x4F95, 0x4FD7,
-    0x5019, 0x505A, 0x509A, 0x50DA, 0x5118, 0x5156, 0x5194, 0x51D0,
-    0x520C, 0x5247, 0x5281, 0x52BA, 0x52F3, 0x532A, 0x5361, 0x5397,
-    0x53CC, 0x5401, 0x5434, 0x5467, 0x5499, 0x54CA, 0x54FA, 0x5529,
-    0x5558, 0x5585, 0x55B2, 0x55DE, 0x5609, 0x5632, 0x565B, 0x5684,
-    0x56AB, 0x56D1, 0x56F6, 0x571B, 0x573E, 0x5761, 0x5782, 0x57A3,
-    0x57C3, 0x57E2, 0x57FF, 0x581C, 0x5838, 0x5853, 0x586D, 0x5886,
-    0x589E, 0x58B5, 0x58CB, 0x58E0, 0x58F4, 0x5907, 0x5919, 0x592A,
-    0x593A, 0x5949, 0x5958, 0x5965, 0x5971, 0x597C, 0x5986, 0x598F,
-    0x5997, 0x599E, 0x59A4, 0x59A9, 0x59AD, 0x59B0, 0x59B2, 0x59B3
-};
-
-psx_spu_t* psx_spu_create() {
-    return (psx_spu_t*)malloc(sizeof(psx_spu_t));
-}
-
-void psx_spu_init(psx_spu_t* spu) {
-    memset(spu, 0, sizeof(psx_spu_t));
-
-    spu->io_base = PSX_SPU_BEGIN;
-    spu->io_size = PSX_SPU_SIZE;
-
-    spu->ram = (uint8_t*)malloc(SPU_RAM_SIZE);
-
-    memset(spu->ram, 0, SPU_RAM_SIZE);
-
-    // Mute all voices
-    spu->endx = 0x00ffffff;
-}
-
-uint32_t psx_spu_read32(psx_spu_t* spu, uint32_t offset) {
-    const uint8_t* ptr = (uint8_t*)&spu->voice[0].volumel;
-
-    return *((uint32_t*)(ptr + offset));
-}
-
-uint16_t psx_spu_read16(psx_spu_t* spu, uint32_t offset) {
-    if (offset == SPUR_TFIFO) {
-        uint16_t data = *(uint16_t*)(&spu->ram[spu->taddr]);
-
-        spu->taddr += 2;
-
-        return data;
-    }
-
-    const uint8_t* ptr = (uint8_t*)&spu->voice[0].volumel;
-
-    return *((uint16_t*)(ptr + offset));
-}
-
-uint8_t psx_spu_read8(psx_spu_t* spu, uint32_t offset) {
-    log_fatal("Unhandled 8-bit SPU read at offset %08x", offset);
-
-    return 0x0;
-}
-
-void spu_read_block(psx_spu_t* spu, int v) {
-    uint32_t addr = spu->data[v].current_addr;
-    uint8_t hdr = spu->ram[addr];
-
-    spu->data[v].block_flags = spu->ram[addr + 1];
-
-    unsigned shift  = 12 - (hdr & 0x0f);
-    unsigned filter = (hdr >> 4) & 7;
-
-    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 = (spu->ram[addr + 2 + (j >> 1)] >> ((j & 1) * 4)) & 0xf;
-
-        // Sign extend t
-        int16_t t = (int16_t)(n << 12) >> 12; 
-        int16_t s = (t << shift) + (((spu->data[v].h[0] * f0) + (spu->data[v].h[1] * f1) + 32) / 64);
-        
-        s = (s < INT16_MIN) ? INT16_MIN : ((s > INT16_MAX) ? INT16_MAX : s);
-
-        spu->data[v].h[1] = spu->data[v].h[0];
-        spu->data[v].h[0] = s;
-        spu->data[v].buf[j] = s;
-    }
-}
-
-#define PHASE spu->data[v].adsr_phase
-#define CYCLES spu->data[v].adsr_cycles
-#define EXPONENTIAL spu->data[v].adsr_mode
-#define DECREASE spu->data[v].adsr_dir
-#define SHIFT spu->data[v].adsr_shift
-#define STEP spu->data[v].adsr_step
-#define LEVEL_STEP spu->data[v].adsr_pending_step
-#define LEVEL spu->data[v].cvol
-
-/*
-  ____lower 16bit (at 1F801C08h+N*10h)___________________________________
-  15    Attack Mode       (0=Linear, 1=Exponential)
-  -     Attack Direction  (Fixed, always Increase) (until Level 7FFFh)
-  14-10 Attack Shift      (0..1Fh = Fast..Slow)
-  9-8   Attack Step       (0..3 = "+7,+6,+5,+4")
-  -     Decay Mode        (Fixed, always Exponential)
-  -     Decay Direction   (Fixed, always Decrease) (until Sustain Level)
-  7-4   Decay Shift       (0..0Fh = Fast..Slow)
-  -     Decay Step        (Fixed, always "-8")
-  3-0   Sustain Level     (0..0Fh)  ;Level=(N+1)*800h
-  ____upper 16bit (at 1F801C0Ah+N*10h)___________________________________
-  31    Sustain Mode      (0=Linear, 1=Exponential)
-  30    Sustain Direction (0=Increase, 1=Decrease) (until Key OFF flag)
-  29    Not used?         (should be zero)
-  28-24 Sustain Shift     (0..1Fh = Fast..Slow)
-  23-22 Sustain Step      (0..3 = "+7,+6,+5,+4" or "-8,-7,-6,-5") (inc/dec)
-  21    Release Mode      (0=Linear, 1=Exponential)
-  -     Release Direction (Fixed, always Decrease) (until Level 0000h)
-  20-16 Release Shift     (0..1Fh = Fast..Slow)
-  -     Release Step      (Fixed, always "-8")
-*/
-
-enum {
-    ADSR_ATTACK,
-    ADSR_DECAY,
-    ADSR_SUSTAIN,
-    ADSR_RELEASE,
-    ADSR_END
-};
-
-void adsr_calculate_values(psx_spu_t* spu, int v) {
-    CYCLES = 1 << MAX(0, SHIFT - 11);
-    LEVEL_STEP = STEP << MAX(0, 11 - SHIFT);
-
-    if (EXPONENTIAL && (LEVEL > 0x6000) && !DECREASE)
-        CYCLES *= 4;
-    
-    if (EXPONENTIAL && DECREASE)
-        LEVEL_STEP = (LEVEL_STEP * LEVEL) >> 15;
-    
-    spu->data[v].adsr_cycles_reload = CYCLES;
-}
-
-void adsr_load_attack(psx_spu_t* spu, int v) {
-    EXPONENTIAL = spu->data[v].envctl >> 15;
-    DECREASE    = 0;
-    SHIFT       = (spu->data[v].envctl >> 10) & 0x1f;
-    STEP        = 7 - ((spu->data[v].envctl >> 8) & 3);
-    LEVEL       = 0;
-    PHASE       = ADSR_ATTACK;
-
-    adsr_calculate_values(spu, v);
-}
-
-void adsr_load_decay(psx_spu_t* spu, int v) {
-    EXPONENTIAL = 1;
-    DECREASE    = 1;
-    SHIFT       = (spu->data[v].envctl >> 4) & 0xf;
-    STEP        = -8;
-    LEVEL       = 0x7fff;
-    PHASE       = ADSR_DECAY;
-
-    adsr_calculate_values(spu, v);
-}
-
-void adsr_load_sustain(psx_spu_t* spu, int v) {
-    EXPONENTIAL = spu->data[v].envctl >> 31;
-    DECREASE    = (spu->data[v].envctl >> 30) & 1;
-    SHIFT       = (spu->data[v].envctl >> 24) & 0x1f;
-    STEP        = (spu->data[v].envctl >> 22) & 3;
-    LEVEL       = spu->data[v].adsr_sustain_level;
-    STEP        = DECREASE ? (-8 + STEP) : (7 - STEP);
-    PHASE       = ADSR_SUSTAIN;
-
-    adsr_calculate_values(spu, v);
-}
-
-void adsr_load_release(psx_spu_t* spu, int v) {
-    EXPONENTIAL = (spu->data[v].envctl >> 21) & 1;
-    DECREASE    = 1;
-    SHIFT       = (spu->data[v].envctl >> 16) & 0x1f;
-    STEP        = -8;
-    PHASE       = ADSR_RELEASE;
-
-    adsr_calculate_values(spu, v);
-}
-
-void spu_handle_adsr(psx_spu_t* spu, int v) {
-    if (CYCLES) {
-        CYCLES -= 1;
-
-        return;
-    }
-
-    LEVEL += LEVEL_STEP;
-
-    switch (spu->data[v].adsr_phase) {
-        case ADSR_ATTACK: {
-            LEVEL = CLAMP(LEVEL, 0x0000, 0x7fff);
-
-            if (LEVEL == 0x7fff)
-                adsr_load_decay(spu, v);
-        } break;
-
-        case ADSR_DECAY: {
-            LEVEL = CLAMP(LEVEL, 0x0000, 0x7fff);
-
-            if (LEVEL <= spu->data[v].adsr_sustain_level)
-                adsr_load_sustain(spu, v);
-        } break;
-
-        case ADSR_SUSTAIN: {
-            LEVEL = CLAMP(LEVEL, 0x0000, 0x7fff);
-
-            /* Not stopped automatically, need to KOFF */
-        } break;
-
-        case ADSR_RELEASE: {
-            LEVEL = CLAMP(LEVEL, 0x0000, 0x7fff);
-
-            if (!LEVEL) {
-                PHASE = ADSR_END;
-                CYCLES = 0;
-                LEVEL_STEP = 0;
-
-                spu->data[v].playing = 0;
-            }
-        } break;
-
-        case ADSR_END: {
-            spu->data[v].playing = 0;
-        } break;
-    }
-
-    spu->voice[v].envcvol = spu->data[v].cvol;
-
-    CYCLES = spu->data[v].adsr_cycles_reload;
-}
-
-#undef PHASE
-#undef CYCLES
-#undef MODE
-#undef DIR
-#undef SHIFT
-#undef STEP
-#undef PENDING_STEP
-
-void spu_kon(psx_spu_t* spu, uint32_t value) {
-    for (int i = 0; i < VOICE_COUNT; i++) {
-        if ((value & (1 << i))) {
-            spu->data[i].playing = 1;
-            spu->data[i].current_addr = spu->voice[i].adsaddr << 3;
-            spu->data[i].repeat_addr = spu->data[i].current_addr;
-            spu->data[i].lvol = (float)(spu->voice[i].volumel << 1) / (float)0x7fff;
-            spu->data[i].rvol = (float)(spu->voice[i].volumer << 1) / (float)0x7fff;
-            spu->data[i].adsr_sustain_level = ((spu->voice[i].envctl1 & 0xf) + 1) * 0x800;
-            spu->data[i].envctl = (((uint32_t)spu->voice[i].envctl2) << 16) |
-                                    (uint32_t)spu->voice[i].envctl1;
-
-            adsr_load_attack(spu, i);
-            spu_read_block(spu, i);
-        }
-    }
-
-    spu->endx &= ~(value & 0x00ffffff);
-}
-
-void spu_koff(psx_spu_t* spu, uint32_t value) {
-    for (int i = 0; i < VOICE_COUNT; i++)
-        if ((value & (1 << i)))
-            adsr_load_release(spu, i);
-}
-
-int spu_handle_write(psx_spu_t* spu, uint32_t offset, uint32_t value) {
-    switch (offset) {
-        case SPUR_KONL: case SPUR_KONH: {
-            int high = (offset & 2) != 0;
-
-            if (!value)
-                return 1;
-
-            spu_kon(spu, value << (16 * high));
-        } return 1;
-
-        case SPUR_KOFFL: case SPUR_KOFFH: {
-            int high = (offset & 2) != 0;
-
-            spu_koff(spu, value << (16 * high));
-        } return 1;
-
-        case SPUR_TADDR: {
-            spu->ramdta = value;
-            spu->taddr = value << 3;
-        } return 1;
-
-        case SPUR_TFIFO: {
-            spu->ramdtf = value;
-            spu->tfifo[spu->tfifo_index++] = value;
-
-            if (spu->tfifo_index == 32) {
-                if (((spu->spucnt >> 4) & 3) == 2) {
-                    for (int i = 0; i < spu->tfifo_index; i++) {
-                        spu->ram[spu->taddr++] = spu->tfifo[i] & 0xff;
-                        spu->ram[spu->taddr++] = spu->tfifo[i] >> 8;
-                    }
-
-                    spu->tfifo_index = 0;
-                }
-            }
-        } return 1;
-
-        case SPUR_SPUCNT: {
-            spu->spucnt = value;
-            spu->spustat &= 0xffc0;
-            spu->spustat |= value & 0x3f;
-
-            if ((value >> 4) & 3) {
-                for (int i = 0; i < spu->tfifo_index; i++) {
-                    spu->ram[spu->taddr++] = spu->tfifo[i] & 0xff;
-                    spu->ram[spu->taddr++] = spu->tfifo[i] >> 8;
-                }
-
-                spu->tfifo_index = 0;
-            }
-        } return 1;
-
-        case SPUR_MBASE: {
-            spu->mbase = value;
-            spu->revbaddr = spu->mbase << 3;
-        } return 1;
-    }
-
-    return 0;
-}
-
-void psx_spu_write32(psx_spu_t* spu, uint32_t offset, uint32_t value) {
-    // Handle special cases first
-    if (spu_handle_write(spu, offset, value))
-        return;
-
-    const uint8_t* ptr = (uint8_t*)&spu->voice[0];
-
-    *((uint32_t*)(ptr + offset)) = value;
-}
-
-void psx_spu_write16(psx_spu_t* spu, uint32_t offset, uint16_t value) {
-    // Handle special cases first
-    if (spu_handle_write(spu, offset, value))
-        return;
-
-    const uint8_t* ptr = (uint8_t*)&spu->voice[0].volumel;
-
-    if (offset != 0x0c) 
-        *((uint16_t*)(ptr + offset)) = value;
-}
-
-void psx_spu_write8(psx_spu_t* spu, uint32_t offset, uint8_t value) {
-    log_fatal("Unhandled 8-bit SPU write at offset %08x (%02x)", offset, value);
-}
-
-void psx_spu_destroy(psx_spu_t* spu) {
-    free(spu->ram);
-    free(spu);
-}
-
-// To-do: Optimize reverb
-
-int16_t spu_read_reverb(psx_spu_t* spu, uint32_t addr) {
-    uint32_t mbase = spu->mbase << 3;
-
-    uint32_t relative = (addr + spu->revbaddr - mbase) % (0x80000 - mbase);
-    uint32_t wrapped = (mbase + relative) & 0x7fffe;
-
-    return *(int16_t*)(spu->ram + wrapped);
-}
-
-void spu_write_reverb(psx_spu_t* spu, uint32_t addr, int16_t value) {
-    uint32_t mbase = spu->mbase << 3;
-
-    uint32_t relative = (addr + spu->revbaddr - mbase) % (0x80000 - mbase);
-    uint32_t wrapped = (mbase + relative) & 0x7fffe;
-
-    *(int16_t*)(spu->ram + wrapped) = value;
-}
-
-#define R16(addr) (spu_read_reverb(spu, addr))
-#define W16(addr, value) spu_write_reverb(spu, addr, value)
-
-void spu_get_reverb_sample(psx_spu_t* spu, int inl, int inr, int* outl, int* outr) {
-    uint32_t mbase = spu->mbase << 3;
-    uint32_t dapf1 = spu->dapf1 << 3;
-    uint32_t dapf2 = spu->dapf2 << 3;
-    uint32_t mlsame = spu->mlsame << 3;
-    uint32_t mrsame = spu->mrsame << 3;
-    uint32_t dlsame = spu->dlsame << 3;
-    uint32_t drsame = spu->drsame << 3;
-    uint32_t mldiff = spu->mldiff << 3;
-    uint32_t mrdiff = spu->mrdiff << 3;
-    uint32_t dldiff = spu->dldiff << 3;
-    uint32_t drdiff = spu->drdiff << 3;
-    uint32_t mlcomb1 = spu->mlcomb1 << 3;
-    uint32_t mlcomb2 = spu->mlcomb2 << 3;
-    uint32_t mlcomb3 = spu->mlcomb3 << 3;
-    uint32_t mlcomb4 = spu->mlcomb4 << 3;
-    uint32_t mrcomb1 = spu->mrcomb1 << 3;
-    uint32_t mrcomb2 = spu->mrcomb2 << 3;
-    uint32_t mrcomb3 = spu->mrcomb3 << 3;
-    uint32_t mrcomb4 = spu->mrcomb4 << 3;
-    uint32_t mlapf1 = spu->mlapf1 << 3;
-    uint32_t mlapf2 = spu->mlapf2 << 3;
-    uint32_t mrapf1 = spu->mrapf1 << 3;
-    uint32_t mrapf2 = spu->mrapf2 << 3;
-
-    float vlin = (float)spu->vlin / 32767.0f;
-    float vrin = (float)spu->vrin / 32767.0f;
-    float viir = (float)spu->viir / 32767.0f;
-    float vwall = (float)spu->vwall / 32767.0f;
-    float vcomb1 = (float)spu->vcomb1 / 32767.0f;
-    float vcomb2 = (float)spu->vcomb2 / 32767.0f;
-    float vcomb3 = (float)spu->vcomb3 / 32767.0f;
-    float vcomb4 = (float)spu->vcomb4 / 32767.0f;
-    float vapf1 = (float)spu->vapf1 / 32767.0f;
-    float vapf2 = (float)spu->vapf2 / 32767.0f;
-    float vlout = (float)spu->vlout / 32767.0f;
-    float vrout = (float)spu->vrout / 32767.0f;
-
-    int lin = ((float)inl * 0.65f) * vlin;
-    int rin = ((float)inr * 0.65f) * vrin;
-
-    int mlsamev = (lin + R16(dlsame)*vwall - R16(mlsame-2))*viir + R16(mlsame-2);
-    int mrsamev = (rin + R16(drsame)*vwall - R16(mrsame-2))*viir + R16(mrsame-2);
-    int mldiffv = (lin + R16(drdiff)*vwall - R16(mldiff-2))*viir + R16(mldiff-2);
-    int mrdiffv = (rin + R16(dldiff)*vwall - R16(mrdiff-2))*viir + R16(mrdiff-2);
-
-    W16(mlsame, CLAMP(mlsamev, -0x8000, 0x7fff));
-    W16(mrsame, CLAMP(mrsamev, -0x8000, 0x7fff));
-    W16(mldiff, CLAMP(mldiffv, -0x8000, 0x7fff));
-    W16(mrdiff, CLAMP(mrdiffv, -0x8000, 0x7fff));
-
-    int lout=vcomb1*R16(mlcomb1)+vcomb2*R16(mlcomb2)+vcomb3*R16(mlcomb3)+vcomb4*R16(mlcomb4);
-    int rout=vcomb1*R16(mrcomb1)+vcomb2*R16(mrcomb2)+vcomb3*R16(mrcomb3)+vcomb4*R16(mrcomb4);
-
-    lout = CLAMP(lout, -0x8000, 0x7fff);
-    rout = CLAMP(rout, -0x8000, 0x7fff);
-
-    lout-=CLAMP(vapf1*R16(mlapf1 - dapf1), -0x8000, 0x7fff); W16(mlapf1, lout); lout*=vapf1+((float)R16(mlapf1 - dapf1) / 32767.0f);
-    rout-=CLAMP(vapf1*R16(mrapf1 - dapf1), -0x8000, 0x7fff); W16(mrapf1, rout); rout*=vapf1+((float)R16(mrapf1 - dapf1) / 32767.0f);
-    lout-=CLAMP(vapf2*R16(mlapf2 - dapf2), -0x8000, 0x7fff); W16(mlapf2, lout); lout*=vapf2+((float)R16(mlapf2 - dapf2) / 32767.0f);
-    rout-=CLAMP(vapf2*R16(mrapf2 - dapf2), -0x8000, 0x7fff); W16(mrapf2, rout); rout*=vapf2+((float)R16(mrapf2 - dapf2) / 32767.0f);
-
-    *outl = lout * vlout;
-    *outr = rout * vrout;
-
-    spu->revbaddr = MAX(mbase, (spu->revbaddr + 2) & 0x7fffe);
-}
-
-#undef R16
-#undef W16
-
-uint32_t psx_spu_get_sample(psx_spu_t* spu) {
-    spu->even_cycle ^= 1;
-
-    int active_voice_count = 0;
-    int left = 0;
-    int right = 0;
-    int revl = 0;
-    int revr = 0;
-
-    for (int v = 0; v < VOICE_COUNT; v++) {
-        if (!spu->data[v].playing)
-            continue;
-
-        spu_handle_adsr(spu, v);
-
-        ++active_voice_count;
-
-        // Shift 3 older samples around
-        spu->data[v].s[3] = spu->data[v].s[2];
-        spu->data[v].s[2] = spu->data[v].s[1];
-        spu->data[v].s[1] = spu->data[v].s[0];
-
-        uint32_t sample_index = spu->data[v].counter >> 12;
-
-        if (sample_index >= 28) {
-            sample_index -= 28;
-
-            spu->data[v].counter &= 0xfff;
-            spu->data[v].counter |= sample_index << 12;
-
-            switch (spu->data[v].block_flags & 3) {
-                case 0: case 2: {
-                    spu->data[v].current_addr += 0x10;
-                } break;
-
-                case 1: {
-                    spu->endx |= (1 << v);
-                    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) {
-                spu->data[v].repeat_addr = spu->data[v].current_addr;
-            }
-
-            spu_read_block(spu, v);
-        }
-
-        // Fetch ADPCM sample
-        spu->data[v].s[0] = spu->data[v].buf[sample_index];
-
-        // Apply 4-point Gaussian interpolation
-        uint8_t gauss_index = (spu->data[v].counter >> 4) & 0xff;
-        int16_t g0 = g_spu_gauss_table[0x0ff - gauss_index];
-        int16_t g1 = g_spu_gauss_table[0x1ff - gauss_index];
-        int16_t g2 = g_spu_gauss_table[0x100 + gauss_index];
-        int16_t g3 = g_spu_gauss_table[0x000 + gauss_index];
-        int16_t out;
-
-        out  = (g0 * spu->data[v].s[3]) >> 15;
-        out += (g1 * spu->data[v].s[2]) >> 15;
-        out += (g2 * spu->data[v].s[1]) >> 15;
-        out += (g3 * spu->data[v].s[0]) >> 15;
-
-        float adsr_vol = (float)spu->voice[v].envcvol / 32767.0f;
-
-        float samplel = (out * spu->data[v].lvol) * adsr_vol * (float)spu->mainlvol / 32767.0f; 
-        float sampler = (out * spu->data[v].rvol) * adsr_vol * (float)spu->mainrvol / 32767.0f; 
-
-        left += samplel;
-        right += sampler;
-
-        if (spu->eon & (1 << v)) {
-            revl += samplel;
-            revr += sampler;
-        }
-
-        uint16_t step = spu->voice[v].adsampr;
-
-        /* To-do: Do pitch modulation here */
-
-        spu->data[v].counter += step;
-    }
-
-    if (!active_voice_count)
-        return 0x00000000;
-    
-    int16_t clamprl = CLAMP(revl, INT16_MIN, INT16_MAX);
-    int16_t clamprr = CLAMP(revr, INT16_MIN, INT16_MAX);
-    int16_t clampsl = CLAMP(left, INT16_MIN, INT16_MAX);
-    int16_t clampsr = CLAMP(right, INT16_MIN, INT16_MAX);
-    
-    if ((spu->spucnt & 0x0080) && spu->even_cycle)
-        spu_get_reverb_sample(spu, clamprl, clamprr, &spu->lrsl, &spu->lrsr);
-
-    uint16_t clampl = CLAMP(clampsl + spu->lrsl, INT16_MIN, INT16_MAX);
-    uint16_t clampr = CLAMP(clampsr + spu->lrsr, INT16_MIN, INT16_MAX);
-
-    return clampl | (((uint32_t)clampr) << 16);
-}
-
-void psx_spu_update_cdda_buffer(psx_spu_t* spu, void* buf) {
-    int16_t* ptr = buf;
-    int16_t* ram = (int16_t*)spu->ram;
-
-    for (int i = 0; i < 0x400;) {
-        ram[i] = ptr[i];
-
-        ++i;
-
-        ram[i + 0x400] = ptr[i];
-
-        ++i;
-    }
-}
-
-#undef CLAMP
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "spu.h"
+#include "../log.h"
+
+#define CLAMP(v, l, h) ((v <= l) ? l : ((v >= h) ? h : v))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+#define VOICE_COUNT 24
+
+static 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
+};
+
+static const int16_t g_spu_gauss_table[] = {
+    -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001,
+    -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001,
+    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001,
+    0x0001, 0x0001, 0x0001, 0x0002, 0x0002, 0x0002, 0x0003, 0x0003,
+    0x0003, 0x0004, 0x0004, 0x0005, 0x0005, 0x0006, 0x0007, 0x0007,
+    0x0008, 0x0009, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E,
+    0x000F, 0x0010, 0x0011, 0x0012, 0x0013, 0x0015, 0x0016, 0x0018,
+    0x0019, 0x001B, 0x001C, 0x001E, 0x0020, 0x0021, 0x0023, 0x0025,
+    0x0027, 0x0029, 0x002C, 0x002E, 0x0030, 0x0033, 0x0035, 0x0038,
+    0x003A, 0x003D, 0x0040, 0x0043, 0x0046, 0x0049, 0x004D, 0x0050,
+    0x0054, 0x0057, 0x005B, 0x005F, 0x0063, 0x0067, 0x006B, 0x006F,
+    0x0074, 0x0078, 0x007D, 0x0082, 0x0087, 0x008C, 0x0091, 0x0096,
+    0x009C, 0x00A1, 0x00A7, 0x00AD, 0x00B3, 0x00BA, 0x00C0, 0x00C7,
+    0x00CD, 0x00D4, 0x00DB, 0x00E3, 0x00EA, 0x00F2, 0x00FA, 0x0101,
+    0x010A, 0x0112, 0x011B, 0x0123, 0x012C, 0x0135, 0x013F, 0x0148,
+    0x0152, 0x015C, 0x0166, 0x0171, 0x017B, 0x0186, 0x0191, 0x019C,
+    0x01A8, 0x01B4, 0x01C0, 0x01CC, 0x01D9, 0x01E5, 0x01F2, 0x0200,
+    0x020D, 0x021B, 0x0229, 0x0237, 0x0246, 0x0255, 0x0264, 0x0273,
+    0x0283, 0x0293, 0x02A3, 0x02B4, 0x02C4, 0x02D6, 0x02E7, 0x02F9,
+    0x030B, 0x031D, 0x0330, 0x0343, 0x0356, 0x036A, 0x037E, 0x0392,
+    0x03A7, 0x03BC, 0x03D1, 0x03E7, 0x03FC, 0x0413, 0x042A, 0x0441,
+    0x0458, 0x0470, 0x0488, 0x04A0, 0x04B9, 0x04D2, 0x04EC, 0x0506,
+    0x0520, 0x053B, 0x0556, 0x0572, 0x058E, 0x05AA, 0x05C7, 0x05E4,
+    0x0601, 0x061F, 0x063E, 0x065C, 0x067C, 0x069B, 0x06BB, 0x06DC,
+    0x06FD, 0x071E, 0x0740, 0x0762, 0x0784, 0x07A7, 0x07CB, 0x07EF,
+    0x0813, 0x0838, 0x085D, 0x0883, 0x08A9, 0x08D0, 0x08F7, 0x091E,
+    0x0946, 0x096F, 0x0998, 0x09C1, 0x09EB, 0x0A16, 0x0A40, 0x0A6C,
+    0x0A98, 0x0AC4, 0x0AF1, 0x0B1E, 0x0B4C, 0x0B7A, 0x0BA9, 0x0BD8,
+    0x0C07, 0x0C38, 0x0C68, 0x0C99, 0x0CCB, 0x0CFD, 0x0D30, 0x0D63,
+    0x0D97, 0x0DCB, 0x0E00, 0x0E35, 0x0E6B, 0x0EA1, 0x0ED7, 0x0F0F,
+    0x0F46, 0x0F7F, 0x0FB7, 0x0FF1, 0x102A, 0x1065, 0x109F, 0x10DB,
+    0x1116, 0x1153, 0x118F, 0x11CD, 0x120B, 0x1249, 0x1288, 0x12C7,
+    0x1307, 0x1347, 0x1388, 0x13C9, 0x140B, 0x144D, 0x1490, 0x14D4,
+    0x1517, 0x155C, 0x15A0, 0x15E6, 0x162C, 0x1672, 0x16B9, 0x1700,
+    0x1747, 0x1790, 0x17D8, 0x1821, 0x186B, 0x18B5, 0x1900, 0x194B,
+    0x1996, 0x19E2, 0x1A2E, 0x1A7B, 0x1AC8, 0x1B16, 0x1B64, 0x1BB3,
+    0x1C02, 0x1C51, 0x1CA1, 0x1CF1, 0x1D42, 0x1D93, 0x1DE5, 0x1E37,
+    0x1E89, 0x1EDC, 0x1F2F, 0x1F82, 0x1FD6, 0x202A, 0x207F, 0x20D4,
+    0x2129, 0x217F, 0x21D5, 0x222C, 0x2282, 0x22DA, 0x2331, 0x2389,
+    0x23E1, 0x2439, 0x2492, 0x24EB, 0x2545, 0x259E, 0x25F8, 0x2653,
+    0x26AD, 0x2708, 0x2763, 0x27BE, 0x281A, 0x2876, 0x28D2, 0x292E,
+    0x298B, 0x29E7, 0x2A44, 0x2AA1, 0x2AFF, 0x2B5C, 0x2BBA, 0x2C18,
+    0x2C76, 0x2CD4, 0x2D33, 0x2D91, 0x2DF0, 0x2E4F, 0x2EAE, 0x2F0D,
+    0x2F6C, 0x2FCC, 0x302B, 0x308B, 0x30EA, 0x314A, 0x31AA, 0x3209,
+    0x3269, 0x32C9, 0x3329, 0x3389, 0x33E9, 0x3449, 0x34A9, 0x3509,
+    0x3569, 0x35C9, 0x3629, 0x3689, 0x36E8, 0x3748, 0x37A8, 0x3807,
+    0x3867, 0x38C6, 0x3926, 0x3985, 0x39E4, 0x3A43, 0x3AA2, 0x3B00,
+    0x3B5F, 0x3BBD, 0x3C1B, 0x3C79, 0x3CD7, 0x3D35, 0x3D92, 0x3DEF,
+    0x3E4C, 0x3EA9, 0x3F05, 0x3F62, 0x3FBD, 0x4019, 0x4074, 0x40D0,
+    0x412A, 0x4185, 0x41DF, 0x4239, 0x4292, 0x42EB, 0x4344, 0x439C,
+    0x43F4, 0x444C, 0x44A3, 0x44FA, 0x4550, 0x45A6, 0x45FC, 0x4651,
+    0x46A6, 0x46FA, 0x474E, 0x47A1, 0x47F4, 0x4846, 0x4898, 0x48E9,
+    0x493A, 0x498A, 0x49D9, 0x4A29, 0x4A77, 0x4AC5, 0x4B13, 0x4B5F,
+    0x4BAC, 0x4BF7, 0x4C42, 0x4C8D, 0x4CD7, 0x4D20, 0x4D68, 0x4DB0,
+    0x4DF7, 0x4E3E, 0x4E84, 0x4EC9, 0x4F0E, 0x4F52, 0x4F95, 0x4FD7,
+    0x5019, 0x505A, 0x509A, 0x50DA, 0x5118, 0x5156, 0x5194, 0x51D0,
+    0x520C, 0x5247, 0x5281, 0x52BA, 0x52F3, 0x532A, 0x5361, 0x5397,
+    0x53CC, 0x5401, 0x5434, 0x5467, 0x5499, 0x54CA, 0x54FA, 0x5529,
+    0x5558, 0x5585, 0x55B2, 0x55DE, 0x5609, 0x5632, 0x565B, 0x5684,
+    0x56AB, 0x56D1, 0x56F6, 0x571B, 0x573E, 0x5761, 0x5782, 0x57A3,
+    0x57C3, 0x57E2, 0x57FF, 0x581C, 0x5838, 0x5853, 0x586D, 0x5886,
+    0x589E, 0x58B5, 0x58CB, 0x58E0, 0x58F4, 0x5907, 0x5919, 0x592A,
+    0x593A, 0x5949, 0x5958, 0x5965, 0x5971, 0x597C, 0x5986, 0x598F,
+    0x5997, 0x599E, 0x59A4, 0x59A9, 0x59AD, 0x59B0, 0x59B2, 0x59B3
+};
+
+psx_spu_t* psx_spu_create() {
+    return (psx_spu_t*)malloc(sizeof(psx_spu_t));
+}
+
+void psx_spu_init(psx_spu_t* spu) {
+    memset(spu, 0, sizeof(psx_spu_t));
+
+    spu->io_base = PSX_SPU_BEGIN;
+    spu->io_size = PSX_SPU_SIZE;
+
+    spu->ram = (uint8_t*)malloc(SPU_RAM_SIZE);
+
+    memset(spu->ram, 0, SPU_RAM_SIZE);
+
+    // Mute all voices
+    spu->endx = 0x00ffffff;
+}
+
+uint32_t psx_spu_read32(psx_spu_t* spu, uint32_t offset) {
+    const uint8_t* ptr = (uint8_t*)&spu->voice[0].volumel;
+
+    return *((uint32_t*)(ptr + offset));
+}
+
+uint16_t psx_spu_read16(psx_spu_t* spu, uint32_t offset) {
+    if (offset == SPUR_TFIFO) {
+        uint16_t data = *(uint16_t*)(&spu->ram[spu->taddr]);
+
+        spu->taddr += 2;
+
+        return data;
+    }
+
+    const uint8_t* ptr = (uint8_t*)&spu->voice[0].volumel;
+
+    return *((uint16_t*)(ptr + offset));
+}
+
+uint8_t psx_spu_read8(psx_spu_t* spu, uint32_t offset) {
+    log_fatal("Unhandled 8-bit SPU read at offset %08x", offset);
+
+    return 0x0;
+}
+
+void spu_read_block(psx_spu_t* spu, int v) {
+    uint32_t addr = spu->data[v].current_addr;
+    uint8_t hdr = spu->ram[addr];
+
+    spu->data[v].block_flags = spu->ram[addr + 1];
+
+    unsigned shift  = 12 - (hdr & 0x0f);
+    unsigned filter = (hdr >> 4) & 7;
+
+    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 = (spu->ram[addr + 2 + (j >> 1)] >> ((j & 1) * 4)) & 0xf;
+
+        // Sign extend t
+        int16_t t = (int16_t)(n << 12) >> 12; 
+        int16_t s = (t << shift) + (((spu->data[v].h[0] * f0) + (spu->data[v].h[1] * f1) + 32) / 64);
+        
+        s = (s < INT16_MIN) ? INT16_MIN : ((s > INT16_MAX) ? INT16_MAX : s);
+
+        spu->data[v].h[1] = spu->data[v].h[0];
+        spu->data[v].h[0] = s;
+        spu->data[v].buf[j] = s;
+    }
+}
+
+#define PHASE spu->data[v].adsr_phase
+#define CYCLES spu->data[v].adsr_cycles
+#define EXPONENTIAL spu->data[v].adsr_mode
+#define DECREASE spu->data[v].adsr_dir
+#define SHIFT spu->data[v].adsr_shift
+#define STEP spu->data[v].adsr_step
+#define LEVEL_STEP spu->data[v].adsr_pending_step
+#define LEVEL spu->data[v].cvol
+
+/*
+  ____lower 16bit (at 1F801C08h+N*10h)___________________________________
+  15    Attack Mode       (0=Linear, 1=Exponential)
+  -     Attack Direction  (Fixed, always Increase) (until Level 7FFFh)
+  14-10 Attack Shift      (0..1Fh = Fast..Slow)
+  9-8   Attack Step       (0..3 = "+7,+6,+5,+4")
+  -     Decay Mode        (Fixed, always Exponential)
+  -     Decay Direction   (Fixed, always Decrease) (until Sustain Level)
+  7-4   Decay Shift       (0..0Fh = Fast..Slow)
+  -     Decay Step        (Fixed, always "-8")
+  3-0   Sustain Level     (0..0Fh)  ;Level=(N+1)*800h
+  ____upper 16bit (at 1F801C0Ah+N*10h)___________________________________
+  31    Sustain Mode      (0=Linear, 1=Exponential)
+  30    Sustain Direction (0=Increase, 1=Decrease) (until Key OFF flag)
+  29    Not used?         (should be zero)
+  28-24 Sustain Shift     (0..1Fh = Fast..Slow)
+  23-22 Sustain Step      (0..3 = "+7,+6,+5,+4" or "-8,-7,-6,-5") (inc/dec)
+  21    Release Mode      (0=Linear, 1=Exponential)
+  -     Release Direction (Fixed, always Decrease) (until Level 0000h)
+  20-16 Release Shift     (0..1Fh = Fast..Slow)
+  -     Release Step      (Fixed, always "-8")
+*/
+
+enum {
+    ADSR_ATTACK,
+    ADSR_DECAY,
+    ADSR_SUSTAIN,
+    ADSR_RELEASE,
+    ADSR_END
+};
+
+void adsr_calculate_values(psx_spu_t* spu, int v) {
+    CYCLES = 1 << MAX(0, SHIFT - 11);
+    LEVEL_STEP = STEP << MAX(0, 11 - SHIFT);
+
+    if (EXPONENTIAL && (LEVEL > 0x6000) && !DECREASE)
+        CYCLES *= 4;
+    
+    if (EXPONENTIAL && DECREASE)
+        LEVEL_STEP = (LEVEL_STEP * LEVEL) >> 15;
+    
+    spu->data[v].adsr_cycles_reload = CYCLES;
+}
+
+void adsr_load_attack(psx_spu_t* spu, int v) {
+    EXPONENTIAL = spu->data[v].envctl >> 15;
+    DECREASE    = 0;
+    SHIFT       = (spu->data[v].envctl >> 10) & 0x1f;
+    STEP        = 7 - ((spu->data[v].envctl >> 8) & 3);
+    LEVEL       = 0;
+    PHASE       = ADSR_ATTACK;
+
+    adsr_calculate_values(spu, v);
+}
+
+void adsr_load_decay(psx_spu_t* spu, int v) {
+    EXPONENTIAL = 1;
+    DECREASE    = 1;
+    SHIFT       = (spu->data[v].envctl >> 4) & 0xf;
+    STEP        = -8;
+    LEVEL       = 0x7fff;
+    PHASE       = ADSR_DECAY;
+
+    adsr_calculate_values(spu, v);
+}
+
+void adsr_load_sustain(psx_spu_t* spu, int v) {
+    EXPONENTIAL = spu->data[v].envctl >> 31;
+    DECREASE    = (spu->data[v].envctl >> 30) & 1;
+    SHIFT       = (spu->data[v].envctl >> 24) & 0x1f;
+    STEP        = (spu->data[v].envctl >> 22) & 3;
+    LEVEL       = spu->data[v].adsr_sustain_level;
+    STEP        = DECREASE ? (-8 + STEP) : (7 - STEP);
+    PHASE       = ADSR_SUSTAIN;
+
+    adsr_calculate_values(spu, v);
+}
+
+void adsr_load_release(psx_spu_t* spu, int v) {
+    EXPONENTIAL = (spu->data[v].envctl >> 21) & 1;
+    DECREASE    = 1;
+    SHIFT       = (spu->data[v].envctl >> 16) & 0x1f;
+    STEP        = -8;
+    PHASE       = ADSR_RELEASE;
+
+    adsr_calculate_values(spu, v);
+}
+
+void spu_handle_adsr(psx_spu_t* spu, int v) {
+    if (CYCLES) {
+        CYCLES -= 1;
+
+        return;
+    }
+
+    LEVEL += LEVEL_STEP;
+
+    switch (spu->data[v].adsr_phase) {
+        case ADSR_ATTACK: {
+            LEVEL = CLAMP(LEVEL, 0x0000, 0x7fff);
+
+            if (LEVEL == 0x7fff)
+                adsr_load_decay(spu, v);
+        } break;
+
+        case ADSR_DECAY: {
+            LEVEL = CLAMP(LEVEL, 0x0000, 0x7fff);
+
+            if (LEVEL <= spu->data[v].adsr_sustain_level)
+                adsr_load_sustain(spu, v);
+        } break;
+
+        case ADSR_SUSTAIN: {
+            LEVEL = CLAMP(LEVEL, 0x0000, 0x7fff);
+
+            /* Not stopped automatically, need to KOFF */
+        } break;
+
+        case ADSR_RELEASE: {
+            LEVEL = CLAMP(LEVEL, 0x0000, 0x7fff);
+
+            if (!LEVEL) {
+                PHASE = ADSR_END;
+                CYCLES = 0;
+                LEVEL_STEP = 0;
+
+                spu->data[v].playing = 0;
+            }
+        } break;
+
+        case ADSR_END: {
+            spu->data[v].playing = 0;
+        } break;
+    }
+
+    spu->voice[v].envcvol = spu->data[v].cvol;
+
+    CYCLES = spu->data[v].adsr_cycles_reload;
+}
+
+#undef PHASE
+#undef CYCLES
+#undef MODE
+#undef DIR
+#undef SHIFT
+#undef STEP
+#undef PENDING_STEP
+
+void spu_kon(psx_spu_t* spu, uint32_t value) {
+    for (int i = 0; i < VOICE_COUNT; i++) {
+        if ((value & (1 << i))) {
+            spu->data[i].playing = 1;
+            spu->data[i].current_addr = spu->voice[i].adsaddr << 3;
+            spu->data[i].repeat_addr = spu->data[i].current_addr;
+            spu->data[i].lvol = (float)(spu->voice[i].volumel << 1) / (float)0x7fff;
+            spu->data[i].rvol = (float)(spu->voice[i].volumer << 1) / (float)0x7fff;
+            spu->data[i].adsr_sustain_level = ((spu->voice[i].envctl1 & 0xf) + 1) * 0x800;
+            spu->data[i].envctl = (((uint32_t)spu->voice[i].envctl2) << 16) |
+                                    (uint32_t)spu->voice[i].envctl1;
+
+            adsr_load_attack(spu, i);
+            spu_read_block(spu, i);
+        }
+    }
+
+    spu->endx &= ~(value & 0x00ffffff);
+}
+
+void spu_koff(psx_spu_t* spu, uint32_t value) {
+    for (int i = 0; i < VOICE_COUNT; i++)
+        if ((value & (1 << i)))
+            adsr_load_release(spu, i);
+}
+
+int spu_handle_write(psx_spu_t* spu, uint32_t offset, uint32_t value) {
+    switch (offset) {
+        case SPUR_KONL: case SPUR_KONH: {
+            int high = (offset & 2) != 0;
+
+            if (!value)
+                return 1;
+
+            spu_kon(spu, value << (16 * high));
+        } return 1;
+
+        case SPUR_KOFFL: case SPUR_KOFFH: {
+            int high = (offset & 2) != 0;
+
+            spu_koff(spu, value << (16 * high));
+        } return 1;
+
+        case SPUR_TADDR: {
+            spu->ramdta = value;
+            spu->taddr = value << 3;
+        } return 1;
+
+        case SPUR_TFIFO: {
+            spu->ramdtf = value;
+            spu->tfifo[spu->tfifo_index++] = value;
+
+            if (spu->tfifo_index == 32) {
+                if (((spu->spucnt >> 4) & 3) == 2) {
+                    for (int i = 0; i < spu->tfifo_index; i++) {
+                        spu->ram[spu->taddr++] = spu->tfifo[i] & 0xff;
+                        spu->ram[spu->taddr++] = spu->tfifo[i] >> 8;
+                    }
+
+                    spu->tfifo_index = 0;
+                }
+            }
+        } return 1;
+
+        case SPUR_SPUCNT: {
+            spu->spucnt = value;
+            spu->spustat &= 0xffc0;
+            spu->spustat |= value & 0x3f;
+
+            if ((value >> 4) & 3) {
+                for (int i = 0; i < spu->tfifo_index; i++) {
+                    spu->ram[spu->taddr++] = spu->tfifo[i] & 0xff;
+                    spu->ram[spu->taddr++] = spu->tfifo[i] >> 8;
+                }
+
+                spu->tfifo_index = 0;
+            }
+        } return 1;
+
+        case SPUR_MBASE: {
+            spu->mbase = value;
+            spu->revbaddr = spu->mbase << 3;
+        } return 1;
+    }
+
+    return 0;
+}
+
+void psx_spu_write32(psx_spu_t* spu, uint32_t offset, uint32_t value) {
+    // Handle special cases first
+    if (spu_handle_write(spu, offset, value))
+        return;
+
+    const uint8_t* ptr = (uint8_t*)&spu->voice[0];
+
+    *((uint32_t*)(ptr + offset)) = value;
+}
+
+void psx_spu_write16(psx_spu_t* spu, uint32_t offset, uint16_t value) {
+    // Handle special cases first
+    if (spu_handle_write(spu, offset, value))
+        return;
+
+    const uint8_t* ptr = (uint8_t*)&spu->voice[0].volumel;
+
+    if (offset != 0x0c) 
+        *((uint16_t*)(ptr + offset)) = value;
+}
+
+void psx_spu_write8(psx_spu_t* spu, uint32_t offset, uint8_t value) {
+    log_fatal("Unhandled 8-bit SPU write at offset %08x (%02x)", offset, value);
+}
+
+void psx_spu_destroy(psx_spu_t* spu) {
+    free(spu->ram);
+    free(spu);
+}
+
+// To-do: Optimize reverb
+
+int16_t spu_read_reverb(psx_spu_t* spu, uint32_t addr) {
+    uint32_t mbase = spu->mbase << 3;
+
+    uint32_t relative = (addr + spu->revbaddr - mbase) % (0x80000 - mbase);
+    uint32_t wrapped = (mbase + relative) & 0x7fffe;
+
+    return *(int16_t*)(spu->ram + wrapped);
+}
+
+void spu_write_reverb(psx_spu_t* spu, uint32_t addr, int16_t value) {
+    uint32_t mbase = spu->mbase << 3;
+
+    uint32_t relative = (addr + spu->revbaddr - mbase) % (0x80000 - mbase);
+    uint32_t wrapped = (mbase + relative) & 0x7fffe;
+
+    *(int16_t*)(spu->ram + wrapped) = value;
+}
+
+#define R16(addr) (spu_read_reverb(spu, addr))
+#define W16(addr, value) spu_write_reverb(spu, addr, value)
+
+void spu_get_reverb_sample(psx_spu_t* spu, int inl, int inr, int* outl, int* outr) {
+    uint32_t mbase = spu->mbase << 3;
+    uint32_t dapf1 = spu->dapf1 << 3;
+    uint32_t dapf2 = spu->dapf2 << 3;
+    uint32_t mlsame = spu->mlsame << 3;
+    uint32_t mrsame = spu->mrsame << 3;
+    uint32_t dlsame = spu->dlsame << 3;
+    uint32_t drsame = spu->drsame << 3;
+    uint32_t mldiff = spu->mldiff << 3;
+    uint32_t mrdiff = spu->mrdiff << 3;
+    uint32_t dldiff = spu->dldiff << 3;
+    uint32_t drdiff = spu->drdiff << 3;
+    uint32_t mlcomb1 = spu->mlcomb1 << 3;
+    uint32_t mlcomb2 = spu->mlcomb2 << 3;
+    uint32_t mlcomb3 = spu->mlcomb3 << 3;
+    uint32_t mlcomb4 = spu->mlcomb4 << 3;
+    uint32_t mrcomb1 = spu->mrcomb1 << 3;
+    uint32_t mrcomb2 = spu->mrcomb2 << 3;
+    uint32_t mrcomb3 = spu->mrcomb3 << 3;
+    uint32_t mrcomb4 = spu->mrcomb4 << 3;
+    uint32_t mlapf1 = spu->mlapf1 << 3;
+    uint32_t mlapf2 = spu->mlapf2 << 3;
+    uint32_t mrapf1 = spu->mrapf1 << 3;
+    uint32_t mrapf2 = spu->mrapf2 << 3;
+
+    float vlin = (float)spu->vlin / 32767.0f;
+    float vrin = (float)spu->vrin / 32767.0f;
+    float viir = (float)spu->viir / 32767.0f;
+    float vwall = (float)spu->vwall / 32767.0f;
+    float vcomb1 = (float)spu->vcomb1 / 32767.0f;
+    float vcomb2 = (float)spu->vcomb2 / 32767.0f;
+    float vcomb3 = (float)spu->vcomb3 / 32767.0f;
+    float vcomb4 = (float)spu->vcomb4 / 32767.0f;
+    float vapf1 = (float)spu->vapf1 / 32767.0f;
+    float vapf2 = (float)spu->vapf2 / 32767.0f;
+    float vlout = (float)spu->vlout / 32767.0f;
+    float vrout = (float)spu->vrout / 32767.0f;
+
+    int lin = ((float)inl * 0.65f) * vlin;
+    int rin = ((float)inr * 0.65f) * vrin;
+
+    int mlsamev = (lin + R16(dlsame)*vwall - R16(mlsame-2))*viir + R16(mlsame-2);
+    int mrsamev = (rin + R16(drsame)*vwall - R16(mrsame-2))*viir + R16(mrsame-2);
+    int mldiffv = (lin + R16(drdiff)*vwall - R16(mldiff-2))*viir + R16(mldiff-2);
+    int mrdiffv = (rin + R16(dldiff)*vwall - R16(mrdiff-2))*viir + R16(mrdiff-2);
+
+    W16(mlsame, CLAMP(mlsamev, -0x8000, 0x7fff));
+    W16(mrsame, CLAMP(mrsamev, -0x8000, 0x7fff));
+    W16(mldiff, CLAMP(mldiffv, -0x8000, 0x7fff));
+    W16(mrdiff, CLAMP(mrdiffv, -0x8000, 0x7fff));
+
+    int lout=vcomb1*R16(mlcomb1)+vcomb2*R16(mlcomb2)+vcomb3*R16(mlcomb3)+vcomb4*R16(mlcomb4);
+    int rout=vcomb1*R16(mrcomb1)+vcomb2*R16(mrcomb2)+vcomb3*R16(mrcomb3)+vcomb4*R16(mrcomb4);
+
+    lout = CLAMP(lout, -0x8000, 0x7fff);
+    rout = CLAMP(rout, -0x8000, 0x7fff);
+
+    lout-=CLAMP(vapf1*R16(mlapf1 - dapf1), -0x8000, 0x7fff); W16(mlapf1, lout); lout*=vapf1+((float)R16(mlapf1 - dapf1) / 32767.0f);
+    rout-=CLAMP(vapf1*R16(mrapf1 - dapf1), -0x8000, 0x7fff); W16(mrapf1, rout); rout*=vapf1+((float)R16(mrapf1 - dapf1) / 32767.0f);
+    lout-=CLAMP(vapf2*R16(mlapf2 - dapf2), -0x8000, 0x7fff); W16(mlapf2, lout); lout*=vapf2+((float)R16(mlapf2 - dapf2) / 32767.0f);
+    rout-=CLAMP(vapf2*R16(mrapf2 - dapf2), -0x8000, 0x7fff); W16(mrapf2, rout); rout*=vapf2+((float)R16(mrapf2 - dapf2) / 32767.0f);
+
+    *outl = lout * vlout;
+    *outr = rout * vrout;
+
+    spu->revbaddr = MAX(mbase, (spu->revbaddr + 2) & 0x7fffe);
+}
+
+#undef R16
+#undef W16
+
+uint32_t psx_spu_get_sample(psx_spu_t* spu) {
+    spu->even_cycle ^= 1;
+
+    int active_voice_count = 0;
+    int left = 0;
+    int right = 0;
+    int revl = 0;
+    int revr = 0;
+
+    for (int v = 0; v < VOICE_COUNT; v++) {
+        if (!spu->data[v].playing)
+            continue;
+
+        spu_handle_adsr(spu, v);
+
+        ++active_voice_count;
+
+        // Shift 3 older samples around
+        spu->data[v].s[3] = spu->data[v].s[2];
+        spu->data[v].s[2] = spu->data[v].s[1];
+        spu->data[v].s[1] = spu->data[v].s[0];
+
+        uint32_t sample_index = spu->data[v].counter >> 12;
+
+        if (sample_index >= 28) {
+            sample_index -= 28;
+
+            spu->data[v].counter &= 0xfff;
+            spu->data[v].counter |= sample_index << 12;
+
+            switch (spu->data[v].block_flags & 3) {
+                case 0: case 2: {
+                    spu->data[v].current_addr += 0x10;
+                } break;
+
+                case 1: {
+                    spu->endx |= (1 << v);
+                    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) {
+                spu->data[v].repeat_addr = spu->data[v].current_addr;
+            }
+
+            spu_read_block(spu, v);
+        }
+
+        // Fetch ADPCM sample
+        spu->data[v].s[0] = spu->data[v].buf[sample_index];
+
+        // Apply 4-point Gaussian interpolation
+        uint8_t gauss_index = (spu->data[v].counter >> 4) & 0xff;
+        int16_t g0 = g_spu_gauss_table[0x0ff - gauss_index];
+        int16_t g1 = g_spu_gauss_table[0x1ff - gauss_index];
+        int16_t g2 = g_spu_gauss_table[0x100 + gauss_index];
+        int16_t g3 = g_spu_gauss_table[0x000 + gauss_index];
+        int16_t out;
+
+        out  = (g0 * spu->data[v].s[3]) >> 15;
+        out += (g1 * spu->data[v].s[2]) >> 15;
+        out += (g2 * spu->data[v].s[1]) >> 15;
+        out += (g3 * spu->data[v].s[0]) >> 15;
+
+        float adsr_vol = (float)spu->voice[v].envcvol / 32767.0f;
+
+        float samplel = (out * spu->data[v].lvol) * adsr_vol * (float)spu->mainlvol / 32767.0f; 
+        float sampler = (out * spu->data[v].rvol) * adsr_vol * (float)spu->mainrvol / 32767.0f; 
+
+        left += samplel;
+        right += sampler;
+
+        if (spu->eon & (1 << v)) {
+            revl += samplel;
+            revr += sampler;
+        }
+
+        uint16_t step = spu->voice[v].adsampr;
+
+        /* To-do: Do pitch modulation here */
+
+        spu->data[v].counter += step;
+    }
+
+    if (!active_voice_count)
+        return 0x00000000;
+    
+    int16_t clamprl = CLAMP(revl, INT16_MIN, INT16_MAX);
+    int16_t clamprr = CLAMP(revr, INT16_MIN, INT16_MAX);
+    int16_t clampsl = CLAMP(left, INT16_MIN, INT16_MAX);
+    int16_t clampsr = CLAMP(right, INT16_MIN, INT16_MAX);
+    
+    if ((spu->spucnt & 0x0080) && spu->even_cycle)
+        spu_get_reverb_sample(spu, clamprl, clamprr, &spu->lrsl, &spu->lrsr);
+
+    uint16_t clampl = CLAMP(clampsl + spu->lrsl, INT16_MIN, INT16_MAX);
+    uint16_t clampr = CLAMP(clampsr + spu->lrsr, INT16_MIN, INT16_MAX);
+
+    return clampl | (((uint32_t)clampr) << 16);
+}
+
+void psx_spu_update_cdda_buffer(psx_spu_t* spu, void* buf) {
+    int16_t* ptr = buf;
+    int16_t* ram = (int16_t*)spu->ram;
+
+    for (int i = 0; i < 0x400;) {
+        ram[i] = ptr[i];
+
+        ++i;
+
+        ram[i + 0x400] = ptr[i];
+
+        ++i;
+    }
+}
+
+#undef CLAMP
 #undef MAX
\ No newline at end of file
--- a/psx/dev/spu.h
+++ b/psx/dev/spu.h
@@ -1,180 +1,180 @@
-#ifndef SPU_H
-#define SPU_H
-
-#include <stdint.h>
-
-#define PSX_SPU_BEGIN 0x1f801c00
-#define PSX_SPU_SIZE  0x400
-#define PSX_SPU_END   0x1f801fff
-
-#define SPU_RAM_SIZE 0x80000
-
-/*
-    1F801D88h - Voice 0..23 Key ON (Start Attack/Decay/Sustain) (KON) (W)
-    1F801D8Ch - Voice 0..23 Key OFF (Start Release) (KOFF) (W)
-    1F801D9Ch - Voice 0..23 ON/OFF (status) (ENDX) (R)
-    1F801DA6h - Sound RAM Data Transfer Address
-    1F801DA8h - Sound RAM Data Transfer Fifo
-    1F801DACh - Sound RAM Data Transfer Control (should be 0004h)
-*/
-
-#define SPUR_KONL    0x188
-#define SPUR_KONH    0x18a
-#define SPUR_KOFFL   0x18c
-#define SPUR_KOFFH   0x18e
-#define SPUR_EONL    0x198
-#define SPUR_EONH    0x19a
-#define SPUR_ENDXL   0x19c
-#define SPUR_ENDXH   0x19e
-#define SPUR_TADDR   0x1a6
-#define SPUR_TFIFO   0x1a8
-#define SPUR_SPUCNT  0x1aa
-#define SPUR_TCTRL   0x1ac
-#define SPUR_SPUSTAT 0x1ae
-#define SPUR_MBASE   0x1a2
-
-typedef struct __attribute__((__packed__)) {
-    uint32_t bus_delay;
-    uint32_t io_base, io_size;
-
-    uint8_t* ram;
-
-    struct __attribute__((__packed__)) {
-        uint16_t volumel;
-        uint16_t volumer;
-        uint16_t adsampr;
-        uint16_t adsaddr;
-        uint16_t envctl1;
-        uint16_t envctl2;
-        uint16_t envcvol;
-        uint16_t adraddr;
-    } voice[24];
-
-    uint16_t mainlvol;
-    uint16_t mainrvol;
-    uint16_t vlout;
-    uint16_t vrout;
-    uint32_t kon;
-    uint32_t koff;
-    uint32_t pmon;
-    uint32_t non;
-    uint32_t eon;
-    uint32_t endx;
-    uint16_t unk_da0;
-    uint16_t mbase;
-    uint16_t irq9addr;
-    uint16_t ramdta;
-    uint16_t ramdtf;
-    uint16_t spucnt;
-    uint16_t ramdtc;
-    uint16_t spustat;
-    uint32_t cdaivol;
-    uint32_t extivol;
-    uint32_t currvol;
-    uint32_t unk_dbc;
-    uint16_t dapf1;
-    uint16_t dapf2;
-    int16_t  viir;
-    int16_t  vcomb1;
-    int16_t  vcomb2;
-    int16_t  vcomb3;
-    int16_t  vcomb4;
-    int16_t  vwall;
-    int16_t  vapf1;
-    int16_t  vapf2;
-    uint16_t mlsame;
-    uint16_t mrsame;
-    uint16_t mlcomb1;
-    uint16_t mrcomb1;
-    uint16_t mlcomb2;
-    uint16_t mrcomb2;
-    uint16_t dlsame;
-    uint16_t drsame;
-    uint16_t mldiff;
-    uint16_t mrdiff;
-    uint16_t mlcomb3;
-    uint16_t mrcomb3;
-    uint16_t mlcomb4;
-    uint16_t mrcomb4;
-    uint16_t dldiff;
-    uint16_t drdiff;
-    uint16_t mlapf1;
-    uint16_t mrapf1;
-    uint16_t mlapf2;
-    uint16_t mrapf2;
-    int16_t  vlin;
-    int16_t  vrin;
-
-    // Internal registers unimplemented
-
-    uint32_t taddr;
-    uint16_t tfifo[32];
-    uint16_t tfifo_index;
-    uint32_t revbaddr;
-    int lrsl;
-    int lrsr;
-    int even_cycle;
-
-    struct {
-        int playing;
-        uint32_t counter;
-        uint32_t current_addr;
-        uint32_t repeat_addr;
-        int16_t s[4];
-        int block_flags;
-        int16_t buf[28];
-        int16_t h[2];
-        float lvol;
-        float rvol;
-        int cvol;
-        int eon;
-
-        /*
-        ____lower 16bit (at 1F801C08h+N*10h)___________________________________
-        15    Attack Mode       (0=Linear, 1=Exponential)
-        -     Attack Direction  (Fixed, always Increase) (until Level 7FFFh)
-        14-10 Attack Shift      (0..1Fh = Fast..Slow)
-        9-8   Attack Step       (0..3 = "+7,+6,+5,+4")
-        -     Decay Mode        (Fixed, always Exponential)
-        -     Decay Direction   (Fixed, always Decrease) (until Sustain Level)
-        7-4   Decay Shift       (0..0Fh = Fast..Slow)
-        -     Decay Step        (Fixed, always "-8")
-        3-0   Sustain Level     (0..0Fh)  ;Level=(N+1)*800h
-        ____upper 16bit (at 1F801C0Ah+N*10h)___________________________________
-        31    Sustain Mode      (0=Linear, 1=Exponential)
-        30    Sustain Direction (0=Increase, 1=Decrease) (until Key OFF flag)
-        29    Not used?         (should be zero)
-        28-24 Sustain Shift     (0..1Fh = Fast..Slow)
-        23-22 Sustain Step      (0..3 = "+7,+6,+5,+4" or "-8,-7,-6,-5") (inc/dec)
-        21    Release Mode      (0=Linear, 1=Exponential)
-        -     Release Direction (Fixed, always Decrease) (until Level 0000h)
-        20-16 Release Shift     (0..1Fh = Fast..Slow)
-        -     Release Step      (Fixed, always "-8")
-        */
-
-        int adsr_phase;
-        int adsr_cycles_reload;
-        int adsr_cycles;
-        int adsr_mode;
-        int adsr_dir;
-        int adsr_shift;
-        int adsr_step;
-        int adsr_pending_step;
-        int adsr_sustain_level;
-        uint32_t envctl;
-    } data[24];
-} psx_spu_t;
-
-psx_spu_t* psx_spu_create();
-void psx_spu_init(psx_spu_t*);
-uint32_t psx_spu_read32(psx_spu_t*, uint32_t);
-uint16_t psx_spu_read16(psx_spu_t*, uint32_t);
-uint8_t psx_spu_read8(psx_spu_t*, uint32_t);
-void psx_spu_write32(psx_spu_t*, uint32_t, uint32_t);
-void psx_spu_write16(psx_spu_t*, uint32_t, uint16_t);
-void psx_spu_write8(psx_spu_t*, uint32_t, uint8_t);
-void psx_spu_destroy(psx_spu_t*);
-void psx_spu_update_cdda_buffer(psx_spu_t*, void*);
-uint32_t psx_spu_get_sample(psx_spu_t*);
-
+#ifndef SPU_H
+#define SPU_H
+
+#include <stdint.h>
+
+#define PSX_SPU_BEGIN 0x1f801c00
+#define PSX_SPU_SIZE  0x400
+#define PSX_SPU_END   0x1f801fff
+
+#define SPU_RAM_SIZE 0x80000
+
+/*
+    1F801D88h - Voice 0..23 Key ON (Start Attack/Decay/Sustain) (KON) (W)
+    1F801D8Ch - Voice 0..23 Key OFF (Start Release) (KOFF) (W)
+    1F801D9Ch - Voice 0..23 ON/OFF (status) (ENDX) (R)
+    1F801DA6h - Sound RAM Data Transfer Address
+    1F801DA8h - Sound RAM Data Transfer Fifo
+    1F801DACh - Sound RAM Data Transfer Control (should be 0004h)
+*/
+
+#define SPUR_KONL    0x188
+#define SPUR_KONH    0x18a
+#define SPUR_KOFFL   0x18c
+#define SPUR_KOFFH   0x18e
+#define SPUR_EONL    0x198
+#define SPUR_EONH    0x19a
+#define SPUR_ENDXL   0x19c
+#define SPUR_ENDXH   0x19e
+#define SPUR_TADDR   0x1a6
+#define SPUR_TFIFO   0x1a8
+#define SPUR_SPUCNT  0x1aa
+#define SPUR_TCTRL   0x1ac
+#define SPUR_SPUSTAT 0x1ae
+#define SPUR_MBASE   0x1a2
+
+typedef struct __attribute__((__packed__)) {
+    uint32_t bus_delay;
+    uint32_t io_base, io_size;
+
+    uint8_t* ram;
+
+    struct __attribute__((__packed__)) {
+        uint16_t volumel;
+        uint16_t volumer;
+        uint16_t adsampr;
+        uint16_t adsaddr;
+        uint16_t envctl1;
+        uint16_t envctl2;
+        uint16_t envcvol;
+        uint16_t adraddr;
+    } voice[24];
+
+    uint16_t mainlvol;
+    uint16_t mainrvol;
+    uint16_t vlout;
+    uint16_t vrout;
+    uint32_t kon;
+    uint32_t koff;
+    uint32_t pmon;
+    uint32_t non;
+    uint32_t eon;
+    uint32_t endx;
+    uint16_t unk_da0;
+    uint16_t mbase;
+    uint16_t irq9addr;
+    uint16_t ramdta;
+    uint16_t ramdtf;
+    uint16_t spucnt;
+    uint16_t ramdtc;
+    uint16_t spustat;
+    uint32_t cdaivol;
+    uint32_t extivol;
+    uint32_t currvol;
+    uint32_t unk_dbc;
+    uint16_t dapf1;
+    uint16_t dapf2;
+    int16_t  viir;
+    int16_t  vcomb1;
+    int16_t  vcomb2;
+    int16_t  vcomb3;
+    int16_t  vcomb4;
+    int16_t  vwall;
+    int16_t  vapf1;
+    int16_t  vapf2;
+    uint16_t mlsame;
+    uint16_t mrsame;
+    uint16_t mlcomb1;
+    uint16_t mrcomb1;
+    uint16_t mlcomb2;
+    uint16_t mrcomb2;
+    uint16_t dlsame;
+    uint16_t drsame;
+    uint16_t mldiff;
+    uint16_t mrdiff;
+    uint16_t mlcomb3;
+    uint16_t mrcomb3;
+    uint16_t mlcomb4;
+    uint16_t mrcomb4;
+    uint16_t dldiff;
+    uint16_t drdiff;
+    uint16_t mlapf1;
+    uint16_t mrapf1;
+    uint16_t mlapf2;
+    uint16_t mrapf2;
+    int16_t  vlin;
+    int16_t  vrin;
+
+    // Internal registers unimplemented
+
+    uint32_t taddr;
+    uint16_t tfifo[32];
+    uint16_t tfifo_index;
+    uint32_t revbaddr;
+    int lrsl;
+    int lrsr;
+    int even_cycle;
+
+    struct {
+        int playing;
+        uint32_t counter;
+        uint32_t current_addr;
+        uint32_t repeat_addr;
+        int16_t s[4];
+        int block_flags;
+        int16_t buf[28];
+        int16_t h[2];
+        float lvol;
+        float rvol;
+        int cvol;
+        int eon;
+
+        /*
+        ____lower 16bit (at 1F801C08h+N*10h)___________________________________
+        15    Attack Mode       (0=Linear, 1=Exponential)
+        -     Attack Direction  (Fixed, always Increase) (until Level 7FFFh)
+        14-10 Attack Shift      (0..1Fh = Fast..Slow)
+        9-8   Attack Step       (0..3 = "+7,+6,+5,+4")
+        -     Decay Mode        (Fixed, always Exponential)
+        -     Decay Direction   (Fixed, always Decrease) (until Sustain Level)
+        7-4   Decay Shift       (0..0Fh = Fast..Slow)
+        -     Decay Step        (Fixed, always "-8")
+        3-0   Sustain Level     (0..0Fh)  ;Level=(N+1)*800h
+        ____upper 16bit (at 1F801C0Ah+N*10h)___________________________________
+        31    Sustain Mode      (0=Linear, 1=Exponential)
+        30    Sustain Direction (0=Increase, 1=Decrease) (until Key OFF flag)
+        29    Not used?         (should be zero)
+        28-24 Sustain Shift     (0..1Fh = Fast..Slow)
+        23-22 Sustain Step      (0..3 = "+7,+6,+5,+4" or "-8,-7,-6,-5") (inc/dec)
+        21    Release Mode      (0=Linear, 1=Exponential)
+        -     Release Direction (Fixed, always Decrease) (until Level 0000h)
+        20-16 Release Shift     (0..1Fh = Fast..Slow)
+        -     Release Step      (Fixed, always "-8")
+        */
+
+        int adsr_phase;
+        int adsr_cycles_reload;
+        int adsr_cycles;
+        int adsr_mode;
+        int adsr_dir;
+        int adsr_shift;
+        int adsr_step;
+        int adsr_pending_step;
+        int adsr_sustain_level;
+        uint32_t envctl;
+    } data[24];
+} psx_spu_t;
+
+psx_spu_t* psx_spu_create();
+void psx_spu_init(psx_spu_t*);
+uint32_t psx_spu_read32(psx_spu_t*, uint32_t);
+uint16_t psx_spu_read16(psx_spu_t*, uint32_t);
+uint8_t psx_spu_read8(psx_spu_t*, uint32_t);
+void psx_spu_write32(psx_spu_t*, uint32_t, uint32_t);
+void psx_spu_write16(psx_spu_t*, uint32_t, uint16_t);
+void psx_spu_write8(psx_spu_t*, uint32_t, uint8_t);
+void psx_spu_destroy(psx_spu_t*);
+void psx_spu_update_cdda_buffer(psx_spu_t*, void*);
+uint32_t psx_spu_get_sample(psx_spu_t*);
+
 #endif
\ No newline at end of file
--- a/psx/dev/timer.c
+++ b/psx/dev/timer.c
@@ -1,371 +1,371 @@
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "timer.h"
-#include "../log.h"
-
-#define T0_COUNTER timer->timer[0].counter
-#define T0_PREV timer->timer[0].prev_counter
-#define T0_MODE timer->timer[0].mode
-#define T0_TARGET timer->timer[0].target
-#define T0_PAUSED timer->timer[0].paused
-#define T0_IRQ_FIRED timer->timer[0].irq_fired
-
-#define T1_COUNTER timer->timer[1].counter
-#define T1_PREV timer->timer[1].prev_counter
-#define T1_MODE timer->timer[1].mode
-#define T1_TARGET timer->timer[1].target
-#define T1_PAUSED timer->timer[1].paused
-#define T1_IRQ_FIRED timer->timer[1].irq_fired
-
-#define T2_COUNTER timer->timer[2].counter
-#define T2_PREV timer->timer[2].prev_counter
-#define T2_MODE timer->timer[2].mode
-#define T2_TARGET timer->timer[2].target
-#define T2_PAUSED timer->timer[2].paused
-#define T2_IRQ_FIRED timer->timer[2].irq_fired
-
-// bool should_I_pause_the_timer(psx_timer_t* timer) {
-//   if ((timer->mode & 1) == 0) return false;
-//   switch ((timer->mode >> 1) & 3) {
-//     case 0: return gpu.isXblank();
-//     case 1: return false;
-//     case 2: return !gpu.isXblank();
-//     case 3: return gpu.gotXblankOnce();
-//   }
-// }
-
-// bool did_timer_reach_target(Timer timer) {
-//   if ((timer.mode & 8) == 1) return timer.value >= timer.target;
-//   return timer.value >= 0xffff;
-// }
-
-// bool should_I_reset_the_timer(Timer timer) {
-//   if (did_timer_reach_target(timer)) return true;
-//   if ((timer.mode & 1) == 0) return false;
-//   switch ((timer.mode >> 1) & 3) {
-//     case 1:
-//     case 2:
-//       return gpu.isXBlank();
-//   }
-//   return false;
-// }
-
-const char* g_psx_timer_reg_names[] = {
-    "counter", 0, 0, 0,
-    "mode", 0, 0, 0,
-    "target", 0, 0, 0
-};
-
-psx_timer_t* psx_timer_create() {
-    return (psx_timer_t*)malloc(sizeof(psx_timer_t));
-}
-
-void psx_timer_init(psx_timer_t* timer, psx_ic_t* ic) {
-    memset(timer, 0, sizeof(psx_timer_t));
-
-    timer->io_base = PSX_TIMER_BEGIN;
-    timer->io_size = PSX_TIMER_SIZE;
-
-    timer->ic = ic;
-}
-
-int t1_counter = 0;
-
-uint32_t psx_timer_read32(psx_timer_t* timer, uint32_t offset) {
-    int index = offset >> 4;
-    int reg = offset & 0xf;
-
-    switch (reg) {
-        case 0: {
-            return timer->timer[index].counter;
-        } break;
-        case 4: {
-            timer->timer[index].mode &= 0xffffe7ff;
-
-            return timer->timer[index].mode;
-        } break;
-        case 8: return timer->timer[index].target;
-    }
-
-    log_fatal("Unhandled 32-bit TIMER read at offset %08x", offset);
-
-    // exit(1);
-
-    return 0x0;
-}
-
-uint16_t psx_timer_read16(psx_timer_t* timer, uint32_t offset) {
-    int index = offset >> 4;
-    int reg = offset & 0xf;
-
-    switch (reg) {
-        case 0: return timer->timer[index].counter;
-        case 4: {
-            timer->timer[index].mode &= 0xffffe7ff;
-
-            return timer->timer[index].mode;
-        } break;
-        case 8: return timer->timer[index].target;
-    }
-
-    log_fatal("Unhandled 16-bit TIMER read at offset %08x", offset);
-
-    return 0x0;
-}
-
-uint8_t psx_timer_read8(psx_timer_t* timer, uint32_t offset) {
-    log_fatal("Unhandled 8-bit TIMER read at offset %08x", offset);
-
-    return 0x0;
-}
-
-void psx_timer_write32(psx_timer_t* timer, uint32_t offset, uint32_t value) {
-    int index = offset >> 4;
-    int reg = offset & 0xf;
-
-    switch (reg) {
-        case 0: {
-            timer->timer[index].counter = value;
-        } return;
-        case 4: {
-            timer->timer[index].mode = value;
-            timer->timer[index].mode |= 0x400;
-            timer->timer[index].irq_fired = 0;
-            timer->timer[index].counter = 0;
-        } return;
-        case 8: timer->timer[index].target = value; return;
-    }
-
-    log_fatal("Unhandled 32-bit TIMER write at offset %08x (%02x)", offset, value);
-
-    // exit(1);
-}
-
-void psx_timer_write16(psx_timer_t* timer, uint32_t offset, uint16_t value) {
-    int index = offset >> 4;
-    int reg = offset & 0xf;
-
-    switch (reg) {
-        case 0: {
-            timer->timer[index].counter = value;
-        } return;
-        case 4: {
-            timer->timer[index].mode = value;
-            timer->timer[index].mode |= 0x400;
-            timer->timer[index].irq_fired = 0;
-            timer->timer[index].counter = 0;
-        } return;
-        case 8: {
-            timer->timer[index].target = value;
-        } return;
-    }
-
-    log_fatal("Unhandled 16-bit TIMER write at offset %08x (%02x)", offset, value);
-
-    // 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);
-}
-
-void timer_update_timer0(psx_timer_t* timer, int cyc) {
-    int reached_target = ((uint32_t)T0_COUNTER + cyc) >= T0_TARGET;
-    int reached_max = ((uint32_t)T0_COUNTER + cyc) >= 0xffff;
-
-    // Dotclock unsupported
-    // if (T0_MODE & 0x100)
-
-    if (!T0_PAUSED)
-        T0_COUNTER += cyc;
-
-    int can_fire_irq = (T0_MODE & MODE_IRQRMD) || !T0_IRQ_FIRED;
-
-    int target_irq = reached_target && (T0_MODE & MODE_TGTIRQ);
-    int max_irq = reached_max && (T0_MODE & MODE_MAXIRQ);
-
-    T0_MODE &= ~0x0800;
-    T0_MODE |= reached_target << 11;
-
-    T0_MODE &= ~0x1000;
-    T0_MODE |= reached_max << 12;
-
-    if ((target_irq || max_irq) && can_fire_irq) {
-        if (T0_MODE & MODE_IRQPMD) {
-            T0_MODE ^= 0x400;
-        } else {
-            T0_MODE |= 0x400;
-        }
-
-        timer->timer[0].irq_fired = 1;
-
-        psx_ic_irq(timer->ic, IC_TIMER0);
-    }
-
-    if (T0_MODE & MODE_RESETC) {
-        if (reached_target)
-            T0_COUNTER -= T0_TARGET;
-    }
-}
-
-void timer_update_timer1(psx_timer_t* timer, int cyc) {
-    int reached_target, reached_max;
-
-    if (T1_MODE & 0x100) {
-        reached_target = T1_COUNTER == T1_TARGET;
-        reached_max = T1_COUNTER == 0xffff;
-    } else {
-        reached_target = ((uint32_t)T1_COUNTER + cyc) >= T1_TARGET;
-        reached_max = ((uint32_t)T1_COUNTER + cyc) >= 0xffff;
-
-        if (!T1_PAUSED)
-            T1_COUNTER += cyc;
-    }
-    
-    int can_fire_irq = (T1_MODE & MODE_IRQRMD) || !T1_IRQ_FIRED;
-
-    int target_irq = reached_target && (T1_MODE & MODE_TGTIRQ);
-    int max_irq = reached_max && (T1_MODE & MODE_MAXIRQ);
-
-    T1_MODE &= ~0x0800;
-    T1_MODE |= reached_target << 11;
-
-    T1_MODE &= ~0x1000;
-    T1_MODE |= reached_max << 12;
-
-    if ((target_irq || max_irq) && can_fire_irq) {
-        if (T1_MODE & MODE_IRQPMD) {
-            T1_MODE ^= 0x400;
-        } else {
-            T1_MODE |= 0x400;
-        }
-
-        T1_IRQ_FIRED = 1;
-
-        psx_ic_irq(timer->ic, IC_TIMER1);
-    }
-
-    if (T1_MODE & MODE_RESETC) {
-        if (reached_target)
-            T1_COUNTER -= T1_TARGET;
-    }
-}
-
-void timer_update_timer2(psx_timer_t* timer, int cyc) {
-    T2_PREV = T2_COUNTER;
-
-    if (!T2_PAUSED)
-        T2_COUNTER += cyc;
-
-    uint16_t reset = (T2_MODE & MODE_RESETC) ? T2_TARGET : 0xffff;
-
-    if ((T2_COUNTER >= reset) && (T2_PREV <= reset)) {
-        T2_COUNTER -= reset;
-        T2_MODE |= 0x800;
-
-        if (reset == 0xffff)
-            T2_MODE |= 0x1000;
-    }
-
-    if ((T2_COUNTER >= 0xffff) && (T2_PREV <= 0xffff)) {
-        T2_COUNTER &= 0xffff;
-        T2_MODE |= 0x1000;
-    }
-}
-
-void psx_timer_update(psx_timer_t* timer, int cyc) {
-    timer_update_timer0(timer, cyc);
-    timer_update_timer1(timer, cyc);
-    timer_update_timer2(timer, cyc);
-}
-
-void psxe_gpu_hblank_event_cb(psx_gpu_t* gpu) {
-    psx_timer_t* timer = gpu->udata[1];
-
-    if (T1_MODE & 0x100 && !T1_PAUSED)
-        T1_COUNTER++;
-
-    if (T0_MODE & MODE_SYNCEN) {
-        switch (T0_MODE & 6) {
-            case 0: {
-                T0_PAUSED = 1;
-            } break;
-
-            case 2: {
-                T0_COUNTER = 0;
-            } break;
-
-            case 4: {
-                T0_COUNTER = 0;
-                T0_PAUSED = 0;
-            } break;
-
-            case 6: {
-                T0_MODE &= ~MODE_SYNCEN;
-            } break;
-        }
-    }
-}
-
-void psxe_gpu_hblank_end_event_cb(psx_gpu_t* gpu) {
-    psx_timer_t* timer = gpu->udata[1];
-
-    if (T0_MODE & MODE_SYNCEN) {
-        switch (T0_MODE & 6) {
-            case 0: {
-                T0_PAUSED = 0;
-            } break;
-
-            case 4: {
-                T0_PAUSED = 1;
-            } break;
-        }
-    }
-}
-
-void psxe_gpu_vblank_timer_event_cb(psx_gpu_t* gpu) {
-    psx_timer_t* timer = gpu->udata[1];
-
-    if (T1_MODE & MODE_SYNCEN) {
-        switch (T1_MODE & 6) {
-            case 0: {
-                T1_PAUSED = 1;
-            } break;
-
-            case 2: {
-                T1_COUNTER = 0;
-            } break;
-
-            case 4: {
-                T1_COUNTER = 0;
-                T1_PAUSED = 0;
-            } break;
-
-            case 6: {
-                T1_MODE &= ~MODE_SYNCEN;
-            } break;
-        }
-    }
-}
-
-void psxe_gpu_vblank_end_event_cb(psx_gpu_t* gpu) {
-    psx_timer_t* timer = gpu->udata[1];
-
-    if (T1_MODE & MODE_SYNCEN) {
-        switch (T1_MODE & 6) {
-            case 0: {
-                T1_PAUSED = 0;
-            } break;
-
-            case 4: {
-                T1_PAUSED = 1;
-            } break;
-        }
-    }
-}
-
-void psx_timer_destroy(psx_timer_t* timer) {
-    free(timer);
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "timer.h"
+#include "../log.h"
+
+#define T0_COUNTER timer->timer[0].counter
+#define T0_PREV timer->timer[0].prev_counter
+#define T0_MODE timer->timer[0].mode
+#define T0_TARGET timer->timer[0].target
+#define T0_PAUSED timer->timer[0].paused
+#define T0_IRQ_FIRED timer->timer[0].irq_fired
+
+#define T1_COUNTER timer->timer[1].counter
+#define T1_PREV timer->timer[1].prev_counter
+#define T1_MODE timer->timer[1].mode
+#define T1_TARGET timer->timer[1].target
+#define T1_PAUSED timer->timer[1].paused
+#define T1_IRQ_FIRED timer->timer[1].irq_fired
+
+#define T2_COUNTER timer->timer[2].counter
+#define T2_PREV timer->timer[2].prev_counter
+#define T2_MODE timer->timer[2].mode
+#define T2_TARGET timer->timer[2].target
+#define T2_PAUSED timer->timer[2].paused
+#define T2_IRQ_FIRED timer->timer[2].irq_fired
+
+// bool should_I_pause_the_timer(psx_timer_t* timer) {
+//   if ((timer->mode & 1) == 0) return false;
+//   switch ((timer->mode >> 1) & 3) {
+//     case 0: return gpu.isXblank();
+//     case 1: return false;
+//     case 2: return !gpu.isXblank();
+//     case 3: return gpu.gotXblankOnce();
+//   }
+// }
+
+// bool did_timer_reach_target(Timer timer) {
+//   if ((timer.mode & 8) == 1) return timer.value >= timer.target;
+//   return timer.value >= 0xffff;
+// }
+
+// bool should_I_reset_the_timer(Timer timer) {
+//   if (did_timer_reach_target(timer)) return true;
+//   if ((timer.mode & 1) == 0) return false;
+//   switch ((timer.mode >> 1) & 3) {
+//     case 1:
+//     case 2:
+//       return gpu.isXBlank();
+//   }
+//   return false;
+// }
+
+const char* g_psx_timer_reg_names[] = {
+    "counter", 0, 0, 0,
+    "mode", 0, 0, 0,
+    "target", 0, 0, 0
+};
+
+psx_timer_t* psx_timer_create() {
+    return (psx_timer_t*)malloc(sizeof(psx_timer_t));
+}
+
+void psx_timer_init(psx_timer_t* timer, psx_ic_t* ic) {
+    memset(timer, 0, sizeof(psx_timer_t));
+
+    timer->io_base = PSX_TIMER_BEGIN;
+    timer->io_size = PSX_TIMER_SIZE;
+
+    timer->ic = ic;
+}
+
+int t1_counter = 0;
+
+uint32_t psx_timer_read32(psx_timer_t* timer, uint32_t offset) {
+    int index = offset >> 4;
+    int reg = offset & 0xf;
+
+    switch (reg) {
+        case 0: {
+            return timer->timer[index].counter;
+        } break;
+        case 4: {
+            timer->timer[index].mode &= 0xffffe7ff;
+
+            return timer->timer[index].mode;
+        } break;
+        case 8: return timer->timer[index].target;
+    }
+
+    log_fatal("Unhandled 32-bit TIMER read at offset %08x", offset);
+
+    // exit(1);
+
+    return 0x0;
+}
+
+uint16_t psx_timer_read16(psx_timer_t* timer, uint32_t offset) {
+    int index = offset >> 4;
+    int reg = offset & 0xf;
+
+    switch (reg) {
+        case 0: return timer->timer[index].counter;
+        case 4: {
+            timer->timer[index].mode &= 0xffffe7ff;
+
+            return timer->timer[index].mode;
+        } break;
+        case 8: return timer->timer[index].target;
+    }
+
+    printf("Unhandled 16-bit TIMER read at offset %08x\n", offset);
+
+    return 0x0;
+}
+
+uint8_t psx_timer_read8(psx_timer_t* timer, uint32_t offset) {
+    printf("Unhandled 8-bit TIMER read at offset %08x\n", offset);
+
+    return 0x0;
+}
+
+void psx_timer_write32(psx_timer_t* timer, uint32_t offset, uint32_t value) {
+    int index = offset >> 4;
+    int reg = offset & 0xf;
+
+    switch (reg) {
+        case 0: {
+            timer->timer[index].counter = value;
+        } return;
+        case 4: {
+            timer->timer[index].mode = value;
+            timer->timer[index].mode |= 0x400;
+            timer->timer[index].irq_fired = 0;
+            timer->timer[index].counter = 0;
+        } return;
+        case 8: timer->timer[index].target = value; return;
+    }
+
+    log_fatal("Unhandled 32-bit TIMER write at offset %08x (%02x)", offset, value);
+
+    // exit(1);
+}
+
+void psx_timer_write16(psx_timer_t* timer, uint32_t offset, uint16_t value) {
+    int index = offset >> 4;
+    int reg = offset & 0xf;
+
+    switch (reg) {
+        case 0: {
+            timer->timer[index].counter = value;
+        } return;
+        case 4: {
+            timer->timer[index].mode = value;
+            timer->timer[index].mode |= 0x400;
+            timer->timer[index].irq_fired = 0;
+            timer->timer[index].counter = 0;
+        } return;
+        case 8: {
+            timer->timer[index].target = value;
+        } return;
+    }
+
+    printf("Unhandled 16-bit TIMER write at offset %08x (%02x)\n", offset, value);
+
+    // exit(1);
+}
+
+void psx_timer_write8(psx_timer_t* timer, uint32_t offset, uint8_t value) {
+    printf("Unhandled 8-bit TIMER write at offset %08x (%02x)\n", offset, value);
+}
+
+void timer_update_timer0(psx_timer_t* timer, int cyc) {
+    int reached_target = ((uint32_t)T0_COUNTER + cyc) >= T0_TARGET;
+    int reached_max = ((uint32_t)T0_COUNTER + cyc) >= 0xffff;
+
+    // Dotclock unsupported
+    // if (T0_MODE & 0x100)
+
+    if (!T0_PAUSED)
+        T0_COUNTER += cyc;
+
+    int can_fire_irq = (T0_MODE & MODE_IRQRMD) || !T0_IRQ_FIRED;
+
+    int target_irq = reached_target && (T0_MODE & MODE_TGTIRQ);
+    int max_irq = reached_max && (T0_MODE & MODE_MAXIRQ);
+
+    T0_MODE &= ~0x0800;
+    T0_MODE |= reached_target << 11;
+
+    T0_MODE &= ~0x1000;
+    T0_MODE |= reached_max << 12;
+
+    if ((target_irq || max_irq) && can_fire_irq) {
+        if (T0_MODE & MODE_IRQPMD) {
+            T0_MODE ^= 0x400;
+        } else {
+            T0_MODE |= 0x400;
+        }
+
+        timer->timer[0].irq_fired = 1;
+
+        psx_ic_irq(timer->ic, IC_TIMER0);
+    }
+
+    if (T0_MODE & MODE_RESETC) {
+        if (reached_target)
+            T0_COUNTER -= T0_TARGET;
+    }
+}
+
+void timer_update_timer1(psx_timer_t* timer, int cyc) {
+    int reached_target, reached_max;
+
+    if (T1_MODE & 0x100) {
+        reached_target = T1_COUNTER == T1_TARGET;
+        reached_max = T1_COUNTER == 0xffff;
+    } else {
+        reached_target = ((uint32_t)T1_COUNTER + cyc) >= T1_TARGET;
+        reached_max = ((uint32_t)T1_COUNTER + cyc) >= 0xffff;
+
+        if (!T1_PAUSED)
+            T1_COUNTER += cyc;
+    }
+    
+    int can_fire_irq = (T1_MODE & MODE_IRQRMD) || !T1_IRQ_FIRED;
+
+    int target_irq = reached_target && (T1_MODE & MODE_TGTIRQ);
+    int max_irq = reached_max && (T1_MODE & MODE_MAXIRQ);
+
+    T1_MODE &= ~0x0800;
+    T1_MODE |= reached_target << 11;
+
+    T1_MODE &= ~0x1000;
+    T1_MODE |= reached_max << 12;
+
+    if ((target_irq || max_irq) && can_fire_irq) {
+        if (T1_MODE & MODE_IRQPMD) {
+            T1_MODE ^= 0x400;
+        } else {
+            T1_MODE |= 0x400;
+        }
+
+        T1_IRQ_FIRED = 1;
+
+        psx_ic_irq(timer->ic, IC_TIMER1);
+    }
+
+    if (T1_MODE & MODE_RESETC) {
+        if (reached_target)
+            T1_COUNTER -= T1_TARGET;
+    }
+}
+
+void timer_update_timer2(psx_timer_t* timer, int cyc) {
+    T2_PREV = T2_COUNTER;
+
+    if (!T2_PAUSED)
+        T2_COUNTER += cyc;
+
+    uint16_t reset = (T2_MODE & MODE_RESETC) ? T2_TARGET : 0xffff;
+
+    if ((T2_COUNTER >= reset) && (T2_PREV <= reset)) {
+        T2_COUNTER -= reset;
+        T2_MODE |= 0x800;
+
+        if (reset == 0xffff)
+            T2_MODE |= 0x1000;
+    }
+
+    if ((T2_COUNTER >= 0xffff) && (T2_PREV <= 0xffff)) {
+        T2_COUNTER &= 0xffff;
+        T2_MODE |= 0x1000;
+    }
+}
+
+void psx_timer_update(psx_timer_t* timer, int cyc) {
+    timer_update_timer0(timer, cyc);
+    timer_update_timer1(timer, cyc);
+    timer_update_timer2(timer, cyc);
+}
+
+void psxe_gpu_hblank_event_cb(psx_gpu_t* gpu) {
+    psx_timer_t* timer = gpu->udata[1];
+
+    if (T1_MODE & 0x100 && !T1_PAUSED)
+        T1_COUNTER++;
+
+    if (T0_MODE & MODE_SYNCEN) {
+        switch (T0_MODE & 6) {
+            case 0: {
+                T0_PAUSED = 1;
+            } break;
+
+            case 2: {
+                T0_COUNTER = 0;
+            } break;
+
+            case 4: {
+                T0_COUNTER = 0;
+                T0_PAUSED = 0;
+            } break;
+
+            case 6: {
+                T0_MODE &= ~MODE_SYNCEN;
+            } break;
+        }
+    }
+}
+
+void psxe_gpu_hblank_end_event_cb(psx_gpu_t* gpu) {
+    psx_timer_t* timer = gpu->udata[1];
+
+    if (T0_MODE & MODE_SYNCEN) {
+        switch (T0_MODE & 6) {
+            case 0: {
+                T0_PAUSED = 0;
+            } break;
+
+            case 4: {
+                T0_PAUSED = 1;
+            } break;
+        }
+    }
+}
+
+void psxe_gpu_vblank_timer_event_cb(psx_gpu_t* gpu) {
+    psx_timer_t* timer = gpu->udata[1];
+
+    if (T1_MODE & MODE_SYNCEN) {
+        switch (T1_MODE & 6) {
+            case 0: {
+                T1_PAUSED = 1;
+            } break;
+
+            case 2: {
+                T1_COUNTER = 0;
+            } break;
+
+            case 4: {
+                T1_COUNTER = 0;
+                T1_PAUSED = 0;
+            } break;
+
+            case 6: {
+                T1_MODE &= ~MODE_SYNCEN;
+            } break;
+        }
+    }
+}
+
+void psxe_gpu_vblank_end_event_cb(psx_gpu_t* gpu) {
+    psx_timer_t* timer = gpu->udata[1];
+
+    if (T1_MODE & MODE_SYNCEN) {
+        switch (T1_MODE & 6) {
+            case 0: {
+                T1_PAUSED = 0;
+            } break;
+
+            case 4: {
+                T1_PAUSED = 1;
+            } break;
+        }
+    }
+}
+
+void psx_timer_destroy(psx_timer_t* timer) {
+    free(timer);
 }
\ No newline at end of file
--- a/psx/dev/timer.h
+++ b/psx/dev/timer.h
@@ -1,93 +1,93 @@
-#ifndef TIMER_H
-#define TIMER_H
-
-#include <stdint.h>
-
-#include "ic.h"
-#include "gpu.h"
-
-#define PSX_TIMER_BEGIN 0x1f801100
-#define PSX_TIMER_SIZE  0x30
-#define PSX_TIMER_END   0x1f80112f
-
-/*
-  0     Synchronization Enable (0=Free Run, 1=Synchronize via Bit1-2)
-  1-2   Synchronization Mode   (0-3, see lists below)
-         Synchronization Modes for Counter 0:
-           0 = Pause counter during Hblank(s)
-           1 = Reset counter to 0000h at Hblank(s)
-           2 = Reset counter to 0000h at Hblank(s) and pause outside of Hblank
-           3 = Pause until Hblank occurs once, then switch to Free Run
-         Synchronization Modes for Counter 1:
-           Same as above, but using Vblank instead of Hblank
-         Synchronization Modes for Counter 2:
-           0 or 3 = Stop counter at current value (forever, no h/v-blank start)
-           1 or 2 = Free Run (same as when Synchronization Disabled)
-  3     Reset counter to 0000h  (0=After Counter=FFFFh, 1=After Counter=Target)
-  4     IRQ when Counter=Target (0=Disable, 1=Enable)
-  5     IRQ when Counter=FFFFh  (0=Disable, 1=Enable)
-  6     IRQ Once/Repeat Mode    (0=One-shot, 1=Repeatedly)
-  7     IRQ Pulse/Toggle Mode   (0=Short Bit10=0 Pulse, 1=Toggle Bit10 on/off)
-  8-9   Clock Source (0-3, see list below)
-         Counter 0:  0 or 2 = System Clock,  1 or 3 = Dotclock
-         Counter 1:  0 or 2 = System Clock,  1 or 3 = Hblank
-         Counter 2:  0 or 1 = System Clock,  2 or 3 = System Clock/8
-  10    Interrupt Request       (0=Yes, 1=No) (Set after Writing)    (W=1) (R)
-  11    Reached Target Value    (0=No, 1=Yes) (Reset after Reading)        (R)
-  12    Reached FFFFh Value     (0=No, 1=Yes) (Reset after Reading)        (R)
-  13-15 Unknown (seems to be always zero)
-  16-31 Garbage (next opcode)
-*/
-#define MODE_SYNCEN 0x0001
-#define MODE_SYNCMD 0x0006
-#define T0MD_HBLPAUSE 0
-#define T0MD_HBLRESET 1
-#define T0MD_HBLRANDP 2
-#define T0MD_HBLOSHOT 3
-// #define T1MD_VBLPAUSE 0
-// #define T1MD_VBLRESET 1
-// #define T1MD_VBLRANDP 2
-// #define T1MD_VBLOSHOT 3
-// #define T2MD_STOPMODE 0
-// #define T2MD_FREEMODE 1
-#define MODE_RESETC 0x0008
-#define MODE_TGTIRQ 0x0010
-#define MODE_MAXIRQ 0x0020
-#define MODE_IRQRMD 0x0040
-#define MODE_IRQPMD 0x0080
-#define MODE_CLK 0x0080
-
-typedef struct {
-    uint32_t bus_delay;
-    uint32_t io_base, io_size;
-
-    psx_ic_t* ic;
-
-    struct {
-        uint16_t prev_counter;
-        uint16_t counter;
-        uint32_t mode;
-        uint32_t target;
-        int paused;
-        int irq_fired;
-    } timer[3];
-} psx_timer_t;
-
-psx_timer_t* psx_timer_create();
-void psx_timer_init(psx_timer_t*, psx_ic_t*);
-uint32_t psx_timer_read32(psx_timer_t*, uint32_t);
-uint16_t psx_timer_read16(psx_timer_t*, uint32_t);
-uint8_t psx_timer_read8(psx_timer_t*, uint32_t);
-void psx_timer_write32(psx_timer_t*, uint32_t, uint32_t);
-void psx_timer_write16(psx_timer_t*, uint32_t, uint16_t);
-void psx_timer_write8(psx_timer_t*, uint32_t, uint8_t);
-void psx_timer_update(psx_timer_t*, int);
-void psx_timer_destroy(psx_timer_t*);
-
-// GPU event handlers
-void psxe_gpu_hblank_event_cb(psx_gpu_t*);
-void psxe_gpu_hblank_end_event_cb(psx_gpu_t*);
-void psxe_gpu_vblank_timer_event_cb(psx_gpu_t*);
-void psxe_gpu_vblank_end_event_cb(psx_gpu_t*);
-
+#ifndef TIMER_H
+#define TIMER_H
+
+#include <stdint.h>
+
+#include "ic.h"
+#include "gpu.h"
+
+#define PSX_TIMER_BEGIN 0x1f801100
+#define PSX_TIMER_SIZE  0x30
+#define PSX_TIMER_END   0x1f80112f
+
+/*
+  0     Synchronization Enable (0=Free Run, 1=Synchronize via Bit1-2)
+  1-2   Synchronization Mode   (0-3, see lists below)
+         Synchronization Modes for Counter 0:
+           0 = Pause counter during Hblank(s)
+           1 = Reset counter to 0000h at Hblank(s)
+           2 = Reset counter to 0000h at Hblank(s) and pause outside of Hblank
+           3 = Pause until Hblank occurs once, then switch to Free Run
+         Synchronization Modes for Counter 1:
+           Same as above, but using Vblank instead of Hblank
+         Synchronization Modes for Counter 2:
+           0 or 3 = Stop counter at current value (forever, no h/v-blank start)
+           1 or 2 = Free Run (same as when Synchronization Disabled)
+  3     Reset counter to 0000h  (0=After Counter=FFFFh, 1=After Counter=Target)
+  4     IRQ when Counter=Target (0=Disable, 1=Enable)
+  5     IRQ when Counter=FFFFh  (0=Disable, 1=Enable)
+  6     IRQ Once/Repeat Mode    (0=One-shot, 1=Repeatedly)
+  7     IRQ Pulse/Toggle Mode   (0=Short Bit10=0 Pulse, 1=Toggle Bit10 on/off)
+  8-9   Clock Source (0-3, see list below)
+         Counter 0:  0 or 2 = System Clock,  1 or 3 = Dotclock
+         Counter 1:  0 or 2 = System Clock,  1 or 3 = Hblank
+         Counter 2:  0 or 1 = System Clock,  2 or 3 = System Clock/8
+  10    Interrupt Request       (0=Yes, 1=No) (Set after Writing)    (W=1) (R)
+  11    Reached Target Value    (0=No, 1=Yes) (Reset after Reading)        (R)
+  12    Reached FFFFh Value     (0=No, 1=Yes) (Reset after Reading)        (R)
+  13-15 Unknown (seems to be always zero)
+  16-31 Garbage (next opcode)
+*/
+#define MODE_SYNCEN 0x0001
+#define MODE_SYNCMD 0x0006
+#define T0MD_HBLPAUSE 0
+#define T0MD_HBLRESET 1
+#define T0MD_HBLRANDP 2
+#define T0MD_HBLOSHOT 3
+// #define T1MD_VBLPAUSE 0
+// #define T1MD_VBLRESET 1
+// #define T1MD_VBLRANDP 2
+// #define T1MD_VBLOSHOT 3
+// #define T2MD_STOPMODE 0
+// #define T2MD_FREEMODE 1
+#define MODE_RESETC 0x0008
+#define MODE_TGTIRQ 0x0010
+#define MODE_MAXIRQ 0x0020
+#define MODE_IRQRMD 0x0040
+#define MODE_IRQPMD 0x0080
+#define MODE_CLK 0x0080
+
+typedef struct {
+    uint32_t bus_delay;
+    uint32_t io_base, io_size;
+
+    psx_ic_t* ic;
+
+    struct {
+        uint16_t prev_counter;
+        uint16_t counter;
+        uint32_t mode;
+        uint32_t target;
+        int paused;
+        int irq_fired;
+    } timer[3];
+} psx_timer_t;
+
+psx_timer_t* psx_timer_create();
+void psx_timer_init(psx_timer_t*, psx_ic_t*);
+uint32_t psx_timer_read32(psx_timer_t*, uint32_t);
+uint16_t psx_timer_read16(psx_timer_t*, uint32_t);
+uint8_t psx_timer_read8(psx_timer_t*, uint32_t);
+void psx_timer_write32(psx_timer_t*, uint32_t, uint32_t);
+void psx_timer_write16(psx_timer_t*, uint32_t, uint16_t);
+void psx_timer_write8(psx_timer_t*, uint32_t, uint8_t);
+void psx_timer_update(psx_timer_t*, int);
+void psx_timer_destroy(psx_timer_t*);
+
+// GPU event handlers
+void psxe_gpu_hblank_event_cb(psx_gpu_t*);
+void psxe_gpu_hblank_end_event_cb(psx_gpu_t*);
+void psxe_gpu_vblank_timer_event_cb(psx_gpu_t*);
+void psxe_gpu_vblank_end_event_cb(psx_gpu_t*);
+
 #endif
\ No newline at end of file
--- a/psx/dev/xa.c
+++ b/psx/dev/xa.c
@@ -1,11 +1,11 @@
-#include "xa.h"
-
-#include <stdint.h>
-
-void xa_decode_audio(uint8_t* src, uint16_t* dst) {
-    // Not a XA sector
-    if (src[XA_HDR_MODE] != 0x02)
-        return;
-    
-    // uint8_t ci = src[XA_SHDR_CODINGINFO];
+#include "xa.h"
+
+#include <stdint.h>
+
+void xa_decode_audio(uint8_t* src, uint16_t* dst) {
+    // Not a XA sector
+    if (src[XA_HDR_MODE] != 0x02)
+        return;
+    
+    // uint8_t ci = src[XA_SHDR_CODINGINFO];
 }
\ No newline at end of file
--- a/psx/dev/xa.h
+++ b/psx/dev/xa.h
@@ -1,69 +1,69 @@
-#ifndef XA_H
-#define XA_H
-
-/*
-  000h 0Ch  Sync
-  00Ch 4    Header (Minute,Second,Sector,Mode=02h)
-  010h 4    Sub-Header (File, Channel, Submode with bit5=1, Codinginfo)
-  014h 4    Copy of Sub-Header
-  018h 914h Data (2324 bytes)
-  92Ch 4    EDC (checksum accross [010h..92Bh]) (or 00000000h if no EDC)
-*/
-
-enum {
-    XA_HDR_MINUTE = 0x0c,
-    XA_HDR_SECOND,
-    XA_HDR_SECTOR,
-    XA_HDR_MODE,
-    XA_SHDR_FILE,
-    XA_SHDR_CHANNEL,
-    XA_SHDR_SUBMODE,
-    XA_SHDR_CODINGINFO
-};
-
-/*
-  0   End of Record (EOR) (all Volume Descriptors, and all sectors with EOF)
-  1   Video     ;\Sector Type (usually ONE of these bits should be set)
-  2   Audio     ; Note: PSX .STR files are declared as Data (not as Video)
-  3   Data      ;/
-  4   Trigger           (for application use)
-  5   Form2             (0=Form1/800h-byte data, 1=Form2, 914h-byte data)
-  6   Real Time (RT)
-  7   End of File (EOF) (or end of Directory/PathTable/VolumeTerminator)
-*/
-
-enum {
-    XA_SM_EOR   = 0x01,
-    XA_SM_VIDEO = 0x02,
-    XA_SM_AUDIO = 0x04,
-    XA_SM_DATA  = 0x08,
-    XA_SM_TRIG  = 0x10,
-    XA_SM_FORM2 = 0x20,
-    XA_SM_RT    = 0x40,
-    XA_SM_EOF   = 0x80
-};
-
-/*
-  0-1 Mono/Stereo     (0=Mono, 1=Stereo, 2-3=Reserved)
-  2-2 Sample Rate     (0=37800Hz, 1=18900Hz, 2-3=Reserved)
-  4-5 Bits per Sample (0=Normal/4bit, 1=8bit, 2-3=Reserved)
-  6   Emphasis        (0=Normal/Off, 1=Emphasis)
-  7   Reserved        (0)
-*/
-
-enum {
-    XA_CI_MODE       = 0x03,
-    XA_CI_SAMPLERATE = 0x0c,
-    XA_CI_BPS        = 0x30,
-    XA_CI_EMPHASIS   = 0x40,
-    XA_CI_MONO       = 0x00,
-    XA_CI_STEREO     = 0x01,
-    XA_CI_37800HZ    = 0x00,
-    XA_CI_18900HZ    = 0x04,
-    XA_CI_4BIT       = 0x00,
-    XA_CI_8BIT       = 0x10
-};
-
-void xa_decode_sector(void*, void*);
-
+#ifndef XA_H
+#define XA_H
+
+/*
+  000h 0Ch  Sync
+  00Ch 4    Header (Minute,Second,Sector,Mode=02h)
+  010h 4    Sub-Header (File, Channel, Submode with bit5=1, Codinginfo)
+  014h 4    Copy of Sub-Header
+  018h 914h Data (2324 bytes)
+  92Ch 4    EDC (checksum accross [010h..92Bh]) (or 00000000h if no EDC)
+*/
+
+enum {
+    XA_HDR_MINUTE = 0x0c,
+    XA_HDR_SECOND,
+    XA_HDR_SECTOR,
+    XA_HDR_MODE,
+    XA_SHDR_FILE,
+    XA_SHDR_CHANNEL,
+    XA_SHDR_SUBMODE,
+    XA_SHDR_CODINGINFO
+};
+
+/*
+  0   End of Record (EOR) (all Volume Descriptors, and all sectors with EOF)
+  1   Video     ;\Sector Type (usually ONE of these bits should be set)
+  2   Audio     ; Note: PSX .STR files are declared as Data (not as Video)
+  3   Data      ;/
+  4   Trigger           (for application use)
+  5   Form2             (0=Form1/800h-byte data, 1=Form2, 914h-byte data)
+  6   Real Time (RT)
+  7   End of File (EOF) (or end of Directory/PathTable/VolumeTerminator)
+*/
+
+enum {
+    XA_SM_EOR   = 0x01,
+    XA_SM_VIDEO = 0x02,
+    XA_SM_AUDIO = 0x04,
+    XA_SM_DATA  = 0x08,
+    XA_SM_TRIG  = 0x10,
+    XA_SM_FORM2 = 0x20,
+    XA_SM_RT    = 0x40,
+    XA_SM_EOF   = 0x80
+};
+
+/*
+  0-1 Mono/Stereo     (0=Mono, 1=Stereo, 2-3=Reserved)
+  2-2 Sample Rate     (0=37800Hz, 1=18900Hz, 2-3=Reserved)
+  4-5 Bits per Sample (0=Normal/4bit, 1=8bit, 2-3=Reserved)
+  6   Emphasis        (0=Normal/Off, 1=Emphasis)
+  7   Reserved        (0)
+*/
+
+enum {
+    XA_CI_MODE       = 0x03,
+    XA_CI_SAMPLERATE = 0x0c,
+    XA_CI_BPS        = 0x30,
+    XA_CI_EMPHASIS   = 0x40,
+    XA_CI_MONO       = 0x00,
+    XA_CI_STEREO     = 0x01,
+    XA_CI_37800HZ    = 0x00,
+    XA_CI_18900HZ    = 0x04,
+    XA_CI_4BIT       = 0x00,
+    XA_CI_8BIT       = 0x10
+};
+
+void xa_decode_sector(void*, void*);
+
 #endif
\ No newline at end of file
--- a/psx/disc.c
+++ b/psx/disc.c
@@ -1,46 +1,46 @@
-/*
-    This file is part of the PSXE Emulator Project
-
-    Disc Reader API
-*/
-
-#include "disc.h"
-
-#include <stdint.h>
-#include <stdlib.h>
-#include <stdio.h>
-
-#define CD_SECTOR_SIZE 0x930
-#define CD_SECTORS_PER_SECOND 75
-
-uint32_t disc_get_addr(msf_t msf) {
-    uint32_t sectors = (((msf.m * 60) + msf.s) * CD_SECTORS_PER_SECOND) + msf.f;
-
-    return sectors * CD_SECTOR_SIZE;
-}
-
-psx_disc_t* psx_disc_create() {
-    return (psx_disc_t*)malloc(sizeof(psx_disc_t));
-}
-
-int psx_disc_seek(psx_disc_t* disc, msf_t msf) {
-    return disc->seek_func(disc->udata, msf);
-}
-
-int psx_disc_read_sector(psx_disc_t* disc, void* buf) {
-    return disc->read_sector_func(disc->udata, buf);
-}
-
-int psx_disc_get_track_addr(psx_disc_t* disc, msf_t* msf, int track) {
-    return disc->get_track_addr_func(disc->udata, msf, track);
-}
-
-int psx_disc_get_track_count(psx_disc_t* disc, int* count) {
-    return disc->get_track_count_func(disc->udata, count);
-}
-
-void psx_disc_destroy(psx_disc_t* disc) {
-    disc->destroy_func(disc->udata);
-
-    free(disc);
+/*
+    This file is part of the PSXE Emulator Project
+
+    Disc Reader API
+*/
+
+#include "disc.h"
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#define CD_SECTOR_SIZE 0x930
+#define CD_SECTORS_PER_SECOND 75
+
+uint32_t disc_get_addr(msf_t msf) {
+    uint32_t sectors = (((msf.m * 60) + msf.s) * CD_SECTORS_PER_SECOND) + msf.f;
+
+    return sectors * CD_SECTOR_SIZE;
+}
+
+psx_disc_t* psx_disc_create() {
+    return (psx_disc_t*)malloc(sizeof(psx_disc_t));
+}
+
+int psx_disc_seek(psx_disc_t* disc, msf_t msf) {
+    return disc->seek_func(disc->udata, msf);
+}
+
+int psx_disc_read_sector(psx_disc_t* disc, void* buf) {
+    return disc->read_sector_func(disc->udata, buf);
+}
+
+int psx_disc_get_track_addr(psx_disc_t* disc, msf_t* msf, int track) {
+    return disc->get_track_addr_func(disc->udata, msf, track);
+}
+
+int psx_disc_get_track_count(psx_disc_t* disc, int* count) {
+    return disc->get_track_count_func(disc->udata, count);
+}
+
+void psx_disc_destroy(psx_disc_t* disc) {
+    disc->destroy_func(disc->udata);
+
+    free(disc);
 }
\ No newline at end of file
--- a/psx/disc.h
+++ b/psx/disc.h
@@ -1,45 +1,45 @@
-/*
-    This file is part of the PSXE Emulator Project
-
-    Disc Reader API
-*/
-
-#ifndef DISC_H
-#define DISC_H
-
-#include <stdint.h>
-#include <stdlib.h>
-#include <stdio.h>
-
-#include "log.h"
-#include "msf.h"
-
-enum {
-    DISC_ERR_TRACK_OUT_OF_BOUNDS = 1,
-    DISC_ERR_ADDR_OUT_OF_BOUNDS
-};
-
-typedef int (*disc_seek_t)(void*, msf_t);
-typedef int (*disc_read_sector_t)(void*, void*);
-typedef int (*disc_get_track_addr_t)(void*, msf_t*, int);
-typedef int (*disc_get_track_count_t)(void*, int*);
-typedef void (*disc_destroy_t)(void*);
-
-typedef struct {
-    void* udata;
-
-    disc_seek_t seek_func;
-    disc_read_sector_t read_sector_func;
-    disc_get_track_addr_t get_track_addr_func;
-    disc_get_track_count_t get_track_count_func;
-    disc_destroy_t destroy_func;
-} psx_disc_t;
-
-psx_disc_t* psx_disc_create();
-int psx_disc_seek(psx_disc_t*, msf_t);
-int psx_disc_read_sector(psx_disc_t*, void*);
-int psx_disc_get_track_addr(psx_disc_t*, msf_t*, int);
-int psx_disc_get_track_count(psx_disc_t*, int*);
-void psx_disc_destroy(psx_disc_t*);
-
+/*
+    This file is part of the PSXE Emulator Project
+
+    Disc Reader API
+*/
+
+#ifndef DISC_H
+#define DISC_H
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "log.h"
+#include "msf.h"
+
+enum {
+    DISC_ERR_TRACK_OUT_OF_BOUNDS = 1,
+    DISC_ERR_ADDR_OUT_OF_BOUNDS
+};
+
+typedef int (*disc_seek_t)(void*, msf_t);
+typedef int (*disc_read_sector_t)(void*, void*);
+typedef int (*disc_get_track_addr_t)(void*, msf_t*, int);
+typedef int (*disc_get_track_count_t)(void*, int*);
+typedef void (*disc_destroy_t)(void*);
+
+typedef struct {
+    void* udata;
+
+    disc_seek_t seek_func;
+    disc_read_sector_t read_sector_func;
+    disc_get_track_addr_t get_track_addr_func;
+    disc_get_track_count_t get_track_count_func;
+    disc_destroy_t destroy_func;
+} psx_disc_t;
+
+psx_disc_t* psx_disc_create();
+int psx_disc_seek(psx_disc_t*, msf_t);
+int psx_disc_read_sector(psx_disc_t*, void*);
+int psx_disc_get_track_addr(psx_disc_t*, msf_t*, int);
+int psx_disc_get_track_count(psx_disc_t*, int*);
+void psx_disc_destroy(psx_disc_t*);
+
 #endif
\ No newline at end of file
--- a/psx/disc/bin.c
+++ b/psx/disc/bin.c
@@ -1,114 +1,114 @@
-/*
-    This file is part of the PSXE Emulator Project
-
-    BIN Loader
-*/
-
-#include "bin.h"
-
-#include <stdlib.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-
-psxd_bin_t* psxd_bin_create() {
-    return (psxd_bin_t*)malloc(sizeof(psxd_bin_t));
-}
-
-void psxd_bin_init(psxd_bin_t* bin) {
-    memset(bin, 0, sizeof(psxd_bin_t));
-}
-
-int psxd_bin_load(psxd_bin_t* bin, const char* path) {
-    log_fatal("Loading CD image...");
-
-    FILE* file = fopen(path, "rb");
-
-    if (ferror(file) || !file) {
-        fclose(file);
-
-        return 1;
-    }
-
-    fseek(file, 0, SEEK_END);
-
-    bin->buf_size = ftell(file);
-    
-    fseek(file, 0, SEEK_SET);
-
-    bin->buf = malloc(bin->buf_size);
-
-    if (!fread(bin->buf, 1, bin->buf_size, file)) {
-        perror("Error reading BIN CD image file data");
-
-        exit(1);
-    }
-
-    msf_from_address(&bin->end, bin->buf_size);
-
-    fclose(file);
-
-    log_fatal("Loaded BIN image, size=%08x, end=%02u:%02u:%02u",
-        bin->buf_size,
-        bin->end.m,
-        bin->end.s,
-        bin->end.f
-    );
-
-    return 0;
-}
-
-int psxd_bin_seek(void* udata, msf_t msf) {
-    psxd_bin_t* bin = udata;
-
-    msf.s -= 2;
-
-    bin->seek_offset = msf_to_address(msf);
-
-    log_fatal("BIN seek to %02u:%02u:%02u (%08x < %08x)", msf.m, msf.s, msf.f, bin->seek_offset, bin->buf_size);
-
-    if (bin->seek_offset >= bin->buf_size)
-        return DISC_ERR_ADDR_OUT_OF_BOUNDS;
-
-    return 0;
-}
-
-int psxd_bin_read_sector(void* udata, void* buf) {
-    psxd_bin_t* bin = udata;
-
-    log_fatal("BIN reading sector at offset %08x", bin->seek_offset);
-
-    memcpy(buf, bin->buf + bin->seek_offset, CD_SECTOR_SIZE);
-
-    return 0;
-}
-
-int psxd_bin_get_track_addr(void* udata, msf_t* msf, int track) {
-    if (track > 1)
-        return DISC_ERR_TRACK_OUT_OF_BOUNDS;
-
-    msf->m = 0;
-    msf->s = 2;
-
-    return 0;
-}
-
-int psxd_bin_get_track_count(void* udata, int* count) {
-    *count = 1;
-
-    return 0;
-}
-
-void psxd_bin_init_disc(psxd_bin_t* bin, psx_disc_t* disc) {
-    disc->udata = bin;
-    disc->seek_func = psxd_bin_seek;
-    disc->read_sector_func = psxd_bin_read_sector;
-    disc->get_track_addr_func = psxd_bin_get_track_addr;
-    disc->get_track_count_func = psxd_bin_get_track_count;
-    disc->destroy_func = (disc_destroy_t)psxd_bin_destroy;
-}
-
-void psxd_bin_destroy(psxd_bin_t* bin) {
-    free(bin->buf);
-    free(bin);
+/*
+    This file is part of the PSXE Emulator Project
+
+    BIN Loader
+*/
+
+#include "bin.h"
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+psxd_bin_t* psxd_bin_create() {
+    return (psxd_bin_t*)malloc(sizeof(psxd_bin_t));
+}
+
+void psxd_bin_init(psxd_bin_t* bin) {
+    memset(bin, 0, sizeof(psxd_bin_t));
+}
+
+int psxd_bin_load(psxd_bin_t* bin, const char* path) {
+    log_fatal("Loading CD image...");
+
+    FILE* file = fopen(path, "rb");
+
+    if (ferror(file) || !file) {
+        fclose(file);
+
+        return 1;
+    }
+
+    fseek(file, 0, SEEK_END);
+
+    bin->buf_size = ftell(file);
+    
+    fseek(file, 0, SEEK_SET);
+
+    bin->buf = malloc(bin->buf_size);
+
+    if (!fread(bin->buf, 1, bin->buf_size, file)) {
+        perror("Error reading BIN CD image file data");
+
+        exit(1);
+    }
+
+    msf_from_address(&bin->end, bin->buf_size);
+
+    fclose(file);
+
+    log_fatal("Loaded BIN image, size=%08x, end=%02u:%02u:%02u",
+        bin->buf_size,
+        bin->end.m,
+        bin->end.s,
+        bin->end.f
+    );
+
+    return 0;
+}
+
+int psxd_bin_seek(void* udata, msf_t msf) {
+    psxd_bin_t* bin = udata;
+
+    msf.s -= 2;
+
+    bin->seek_offset = msf_to_address(msf);
+
+    log_fatal("BIN seek to %02u:%02u:%02u (%08x < %08x)", msf.m, msf.s, msf.f, bin->seek_offset, bin->buf_size);
+
+    if (bin->seek_offset >= bin->buf_size)
+        return DISC_ERR_ADDR_OUT_OF_BOUNDS;
+
+    return 0;
+}
+
+int psxd_bin_read_sector(void* udata, void* buf) {
+    psxd_bin_t* bin = udata;
+
+    log_fatal("BIN reading sector at offset %08x", bin->seek_offset);
+
+    memcpy(buf, bin->buf + bin->seek_offset, CD_SECTOR_SIZE);
+
+    return 0;
+}
+
+int psxd_bin_get_track_addr(void* udata, msf_t* msf, int track) {
+    if (track > 1)
+        return DISC_ERR_TRACK_OUT_OF_BOUNDS;
+
+    msf->m = 0;
+    msf->s = 2;
+
+    return 0;
+}
+
+int psxd_bin_get_track_count(void* udata, int* count) {
+    *count = 1;
+
+    return 0;
+}
+
+void psxd_bin_init_disc(psxd_bin_t* bin, psx_disc_t* disc) {
+    disc->udata = bin;
+    disc->seek_func = psxd_bin_seek;
+    disc->read_sector_func = psxd_bin_read_sector;
+    disc->get_track_addr_func = psxd_bin_get_track_addr;
+    disc->get_track_count_func = psxd_bin_get_track_count;
+    disc->destroy_func = (disc_destroy_t)psxd_bin_destroy;
+}
+
+void psxd_bin_destroy(psxd_bin_t* bin) {
+    free(bin->buf);
+    free(bin);
 }
\ No newline at end of file
--- a/psx/disc/bin.h
+++ b/psx/disc/bin.h
@@ -1,30 +1,30 @@
-/*
-    This file is part of the PSXE Emulator Project
-
-    BIN Loader
-*/
-
-#ifndef BIN_H
-#define BIN_H
-
-#include "../disc.h"
-
-#include <stdlib.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-
-typedef struct {
-    char* buf;
-    uint32_t buf_size;
-    uint32_t seek_offset;
-    msf_t end;
-} psxd_bin_t;
-
-psxd_bin_t* psxd_bin_create();
-void psxd_bin_init(psxd_bin_t*);
-int psxd_bin_load(psxd_bin_t*, const char*);
-void psxd_bin_init_disc(psxd_bin_t*, psx_disc_t*);
-void psxd_bin_destroy(psxd_bin_t*);
-
+/*
+    This file is part of the PSXE Emulator Project
+
+    BIN Loader
+*/
+
+#ifndef BIN_H
+#define BIN_H
+
+#include "../disc.h"
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+typedef struct {
+    char* buf;
+    uint32_t buf_size;
+    uint32_t seek_offset;
+    msf_t end;
+} psxd_bin_t;
+
+psxd_bin_t* psxd_bin_create();
+void psxd_bin_init(psxd_bin_t*);
+int psxd_bin_load(psxd_bin_t*, const char*);
+void psxd_bin_init_disc(psxd_bin_t*, psx_disc_t*);
+void psxd_bin_destroy(psxd_bin_t*);
+
 #endif
\ No newline at end of file
--- a/psx/disc/cue.c
+++ b/psx/disc/cue.c
@@ -1,495 +1,495 @@
-/*
-    This file is part of the PSXE Emulator Project
-
-    CUE Parser + Loader
-*/
-
-#include <stdint.h>
-#include <stdio.h>
-#include <stddef.h>
-#include <string.h>
-#include <ctype.h>
-
-#include "cue.h"
-#include "../log.h"
-
-#define CUE_BUF_SIZE 256
-
-static const char* g_psxd_cue_errors[] = {
-    "PE_NO_ERROR",
-    "PE_EXPECTED_KEYWORD",
-    "PE_EXPECTED_STRING",
-    "PE_EXPECTED_NUMBER",
-    "PE_EXPECTED_COLON",
-    "PE_NON_SEQUENTIAL_TRACKS",
-    "PE_UNEXPECTED_TOKEN"
-};
-
-static const char* g_psxd_cue_tokens[] = {
-    "4CH",
-    "AIFF",
-    "AUDIO",
-    "BINARY",
-    "CATALOG",
-    "CDG",
-    "CDI_2336",
-    "CDI_2352",
-    "CDTEXTFILE",
-    "DCP",
-    "FILE",
-    "FLAGS",
-    "INDEX",
-    "ISRC",
-    "MODE1_2048",
-    "MODE1_2352",
-    "MODE2_2336",
-    "MODE2_2352",
-    "MOTOROLA",
-    "MP3",
-    "PERFORMER",
-    "POSTGAP",
-    "PRE",
-    "PREGAP",
-    "REM",
-    "SCMS",
-    "SONGWRITER",
-    "TITLE",
-    "TRACK",
-    "WAVE",
-    0
-};
-
-#define EXPECT_KEYWORD(kw) \
-    if (cue_parse_keyword(cue)) \
-        return cue->error; \
-    if (cue_get_keyword(cue) != kw) \
-        ERROR_OUT(PE_UNEXPECTED_TOKEN);
-
-void cue_add_track(psxd_cue_t* cue) {
-    ++cue->num_tracks;
-
-    cue_track_t** new_track = realloc(cue->track, cue->num_tracks * sizeof(cue_track_t*));
-
-    if (!new_track) {
-        printf("Fatal error: Couldn't allocate a new CUE track\n");
-
-        exit(1);
-    }
-
-    cue->track = new_track;
-
-    cue->track[cue->num_tracks - 1] = malloc(sizeof(cue_track_t));
-
-    memset(cue->track[cue->num_tracks - 1], 0, sizeof(cue_track_t));
-}
-
-void* cue_alloc_block(void* buf, size_t* block_size, size_t ext) {
-    *block_size += ext;
-
-    return realloc(buf, *block_size);
-}
-
-void cue_ignore_whitespace(psxd_cue_t* cue) {
-    while (isspace(cue->c))
-        cue->c = fgetc(cue->file);
-}
-
-int cue_get_keyword(psxd_cue_t* cue) {
-    int i = 0;
-
-    const char* token = g_psxd_cue_tokens[i];
-
-    while (token) {
-        if (!strcmp(token, cue->buf)) {
-            return i;
-        } else {
-            token = g_psxd_cue_tokens[++i];
-        }
-    }
-
-    return -1;
-}
-
-#define ERROR_OUT(err) \
-    { cue->error = err; return err; }
-
-int cue_parse_keyword(psxd_cue_t* cue) {
-    if (!isalpha(cue->c))
-        ERROR_OUT(PE_EXPECTED_KEYWORD);
-    
-    while (isalnum(cue->c) || (cue->c == '/')) {
-        *cue->ptr++ = cue->c;
-
-        cue->c = fgetc(cue->file);
-    }
-
-    *cue->ptr = 0;
-
-    cue->ptr = cue->buf;
-
-    cue_ignore_whitespace(cue);
-
-    return 0;
-}
-
-int cue_parse_string(psxd_cue_t* cue) {
-    if (cue->c != '\"')
-        ERROR_OUT(PE_EXPECTED_STRING);
-
-    cue->c = fgetc(cue->file);
-
-    while (cue->c != '\"') {
-        *cue->ptr++ = cue->c;
-
-        cue->c = fgetc(cue->file);
-    }
-
-    *cue->ptr = 0;
-
-    cue->c = fgetc(cue->file);
-
-    cue->ptr = cue->buf;
-
-    cue_ignore_whitespace(cue);
-
-    return 0;
-}
-
-int cue_parse_number(psxd_cue_t* cue) {
-    if (!isdigit(cue->c))
-        ERROR_OUT(PE_EXPECTED_NUMBER);
-    
-    while (isdigit(cue->c)) {
-        *cue->ptr++ = cue->c;
-
-        cue->c = fgetc(cue->file);
-    }
-
-    *cue->ptr = 0;
-
-    cue->ptr = cue->buf;
-
-    cue_ignore_whitespace(cue);
-
-    return 0;
-}
-
-int cue_parse_msf(psxd_cue_t* cue, msf_t* msf) {
-    if (cue_parse_number(cue))
-        return cue->error;
-    
-    if (cue->c != ':')
-        ERROR_OUT(PE_EXPECTED_COLON);
-    
-    cue->c = fgetc(cue->file);
-
-    msf->m = atoi(cue->buf);
-
-    if (cue_parse_number(cue))
-        return cue->error;
-
-    if (cue->c != ':')
-        ERROR_OUT(PE_EXPECTED_COLON);
-    
-    cue->c = fgetc(cue->file);
-    
-    msf->s = atoi(cue->buf);
-
-    if (cue_parse_number(cue))
-        return cue->error;
-    
-    msf->f = atoi(cue->buf);
-
-    return 0;
-}
-
-int cue_parse(psxd_cue_t* cue, FILE* file) {
-    cue->file = file;
-    cue->c = fgetc(file);
-
-    msf_t msf;
-
-    EXPECT_KEYWORD(CUE_FILE);
-
-    parse_file:
-
-    if (cue_parse_string(cue))
-        return cue->error;
-
-    strcpy(cue->current_file, cue->buf);
-
-    EXPECT_KEYWORD(CUE_BINARY);
-    EXPECT_KEYWORD(CUE_TRACK);
-
-    parse_track:
-
-    if (cue_parse_number(cue))
-        return cue->error;
-    
-    int track = atoi(cue->buf) - 1;
-
-    if (track != cue->num_tracks)
-        ERROR_OUT(PE_NON_SEQUENTIAL_TRACKS);
-    
-    cue_add_track(cue);
-
-    cue->track[track]->filename = malloc(strlen(cue->current_file) + 1);
-
-    // Copy current file to track filename
-    strcpy(cue->track[track]->filename, cue->current_file);
-
-    if (cue_parse_keyword(cue))
-        return cue->error;
-
-    cue->track[track]->type = cue_get_keyword(cue);
-
-    // Expecting at least 1 index
-    EXPECT_KEYWORD(CUE_INDEX);
-
-    parse_index:
-
-    if (cue_parse_number(cue))
-        return cue->error;
-    
-    int index = atoi(cue->buf);
-
-    if (cue_parse_msf(cue, &msf))
-        return cue->error;
-
-    cue->track[track]->index[index] = msf;
-
-    if (feof(cue->file)) {
-        fclose(file);
-
-        return 0;
-    }
-
-    if (cue_parse_keyword(cue))
-        return cue->error;
-    
-    switch (cue_get_keyword(cue)) {
-        case CUE_INDEX:
-            goto parse_index;
-
-        case CUE_FILE:
-            goto parse_file;
-
-        case CUE_TRACK:
-            goto parse_track;
-
-        default:
-            ERROR_OUT(PE_UNEXPECTED_TOKEN);
-    }
-}
-
-psxd_cue_t* psxd_cue_create() {
-    return (psxd_cue_t*)malloc(sizeof(psxd_cue_t));
-}
-
-void psxd_cue_init(psxd_cue_t* cue) {
-    memset(cue, 0, sizeof(psxd_cue_t));
-
-    cue->buf = malloc(CUE_BUF_SIZE);
-    cue->ptr = cue->buf;
-    cue->current_file = malloc(CUE_BUF_SIZE);
-}
-
-char* cue_get_directory(const char* path) {
-    const char* ptr = &path[strlen(path) - 1];
-    char* dir = NULL;
-
-    while ((*ptr != '/') && (*ptr != '\\') && (ptr != path))
-        ptr--;
-    
-    // If no directory specified, assume CWD
-    if (ptr == path) {
-        dir = malloc(3);
-
-        strcpy(dir, ".\\");
-
-        return dir;
-    }
-    
-    ptrdiff_t len = (ptr - path) + 1;
-
-    dir = malloc(len + 1);
-
-    strncpy(dir, path, len);
-
-    dir[len] = 0;
-
-    return dir;
-}
-
-int psxd_cue_load(psxd_cue_t* cue, const char* path) {
-    FILE* file = fopen(path, "rb");
-
-    if (!file) {
-        log_fatal("Couldn't open file \'%s\'", path);
-
-        return 1;
-    }
-
-    log_fatal("Parsing CUE...");
-
-    if (cue_parse(cue, file)) {
-        log_fatal("CUE error %s (%u)",
-            g_psxd_cue_errors[cue->error],
-            cue->error
-        );
-
-        exit(1);
-    }
-
-    log_fatal("Loading CD image...");
-
-    size_t offset = 0;
-
-    char* directory = cue_get_directory(path);
-    size_t directory_len = strlen(directory);
-
-    for (int i = 0; i < cue->num_tracks; i++) {
-        cue_track_t* track = cue->track[i];
-
-        int len = strlen(track->filename) + directory_len;
-
-        char* full_path = malloc(len + 2);
-
-        strcpy(full_path, directory);
-        strcat(full_path, track->filename);
-
-        log_fatal("Loading track %u at \'%s\'...", i + 1, full_path);
-
-        FILE* track_file = fopen(full_path, "rb");
-
-        if (ferror(track_file) || !track_file) {
-            fclose(track_file);
-
-            return 1;
-        }
-
-        uint32_t data_offset = msf_to_address(track->index[1]);
-
-        // Get track size
-        fseek(track_file, 0, SEEK_END);
-
-        // Account for index 1 offset
-        track->size = ftell(track_file);
-
-        cue->buf_size += track->size;
-
-        // Calculate track MS(F)
-        msf_from_address(&track->disc_offset, offset + data_offset);
-        msf_add_s(&track->disc_offset, 2);
-
-        cue->buf = cue_alloc_block(cue->buf, &offset, track->size);
-
-        fseek(track_file, 0, SEEK_SET);
-        
-        if (!fread(cue->buf + (offset - track->size), 1, track->size, track_file)) {
-            perror("Error reading CUE image file data");
-
-            exit(1);
-        }
-
-        fclose(track_file);
-        free(full_path);
-    }
-
-    // Calculate disc end MSF
-    msf_from_address(&cue->end, offset);
-    msf_add_s(&cue->end, 2);
-
-    free(directory);
-
-    log_fatal("Loaded CUE image, size=%08x, end=%02u:%02u:%02u",
-        cue->buf_size,
-        cue->end.m,
-        cue->end.s,
-        cue->end.f
-    );
-
-    return 0;
-}
-
-int psxd_cue_seek(void* udata, msf_t msf) {
-    psxd_cue_t* cue = udata;
-
-    // To-do: Check for OOB seeks
-
-    uint32_t sectors = (((msf.m * 60) + msf.s - 2) * CD_SECTORS_PS) + msf.f;
-
-    cue->seek_offset = sectors * CD_SECTOR_SIZE;
-
-    // log_fatal("CUE seek to %02u:%02u:%02u (%08x < %08x)", msf.m, msf.s, msf.f, cue->seek_offset, cue->buf_size);
-
-    if (cue->seek_offset >= cue->buf_size)
-        return DISC_ERR_ADDR_OUT_OF_BOUNDS;
-
-    return 0;
-}
-
-int psxd_cue_read_sector(void* udata, void* buf) {
-    psxd_cue_t* cue = udata;
-
-    log_fatal("Reading sector at offset %08x", cue->seek_offset);
-
-    memcpy(buf, cue->buf + cue->seek_offset, CD_SECTOR_SIZE);
-
-    return 0;
-}
-
-int psxd_cue_get_track_addr(void* udata, msf_t* msf, int track) {
-    psxd_cue_t* cue = udata;
-
-    track = BTOI(track);
-
-    if (track > cue->num_tracks)
-        return DISC_ERR_TRACK_OUT_OF_BOUNDS;
-
-    if (!msf)
-        return 0;
-
-    if (!track) {
-        msf->m = cue->end.m;
-        msf->s = cue->end.s;
-        msf->f = 0;
-
-        return 0;
-    }
-
-    msf->m = cue->track[track - 1]->disc_offset.m;
-    msf->s = cue->track[track - 1]->disc_offset.s;
-    msf->f = 0;
-
-    return 0;
-}
-
-int psxd_cue_get_track_count(void* udata, int* count) {
-    psxd_cue_t* cue = udata;
-
-    *count = ITOB(cue->num_tracks);
-
-    return 0;
-}
-
-void psxd_cue_init_disc(psxd_cue_t* cue, psx_disc_t* disc) {
-    disc->udata = cue;
-    disc->seek_func = psxd_cue_seek;
-    disc->read_sector_func = psxd_cue_read_sector;
-    disc->get_track_addr_func = psxd_cue_get_track_addr;
-    disc->get_track_count_func = psxd_cue_get_track_count;
-    disc->destroy_func = (disc_destroy_t)psxd_cue_destroy;
-}
-
-void psxd_cue_destroy(psxd_cue_t* cue) {
-    for (int i = 0; i < cue->num_tracks; i++) {
-        free(cue->track[i]->filename);
-        free(cue->track[i]);
-    }
-
-    free(cue->track);
-    free(cue->current_file);
-    free(cue->buf);
-    free(cue);
+/*
+    This file is part of the PSXE Emulator Project
+
+    CUE Parser + Loader
+*/
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "cue.h"
+#include "../log.h"
+
+#define CUE_BUF_SIZE 256
+
+static const char* g_psxd_cue_errors[] = {
+    "PE_NO_ERROR",
+    "PE_EXPECTED_KEYWORD",
+    "PE_EXPECTED_STRING",
+    "PE_EXPECTED_NUMBER",
+    "PE_EXPECTED_COLON",
+    "PE_NON_SEQUENTIAL_TRACKS",
+    "PE_UNEXPECTED_TOKEN"
+};
+
+static const char* g_psxd_cue_tokens[] = {
+    "4CH",
+    "AIFF",
+    "AUDIO",
+    "BINARY",
+    "CATALOG",
+    "CDG",
+    "CDI_2336",
+    "CDI_2352",
+    "CDTEXTFILE",
+    "DCP",
+    "FILE",
+    "FLAGS",
+    "INDEX",
+    "ISRC",
+    "MODE1_2048",
+    "MODE1_2352",
+    "MODE2_2336",
+    "MODE2_2352",
+    "MOTOROLA",
+    "MP3",
+    "PERFORMER",
+    "POSTGAP",
+    "PRE",
+    "PREGAP",
+    "REM",
+    "SCMS",
+    "SONGWRITER",
+    "TITLE",
+    "TRACK",
+    "WAVE",
+    0
+};
+
+#define EXPECT_KEYWORD(kw) \
+    if (cue_parse_keyword(cue)) \
+        return cue->error; \
+    if (cue_get_keyword(cue) != kw) \
+        ERROR_OUT(PE_UNEXPECTED_TOKEN);
+
+void cue_add_track(psxd_cue_t* cue) {
+    ++cue->num_tracks;
+
+    cue_track_t** new_track = realloc(cue->track, cue->num_tracks * sizeof(cue_track_t*));
+
+    if (!new_track) {
+        printf("Fatal error: Couldn't allocate a new CUE track\n");
+
+        exit(1);
+    }
+
+    cue->track = new_track;
+
+    cue->track[cue->num_tracks - 1] = malloc(sizeof(cue_track_t));
+
+    memset(cue->track[cue->num_tracks - 1], 0, sizeof(cue_track_t));
+}
+
+void* cue_alloc_block(void* buf, size_t* block_size, size_t ext) {
+    *block_size += ext;
+
+    return realloc(buf, *block_size);
+}
+
+void cue_ignore_whitespace(psxd_cue_t* cue) {
+    while (isspace(cue->c))
+        cue->c = fgetc(cue->file);
+}
+
+int cue_get_keyword(psxd_cue_t* cue) {
+    int i = 0;
+
+    const char* token = g_psxd_cue_tokens[i];
+
+    while (token) {
+        if (!strcmp(token, cue->buf)) {
+            return i;
+        } else {
+            token = g_psxd_cue_tokens[++i];
+        }
+    }
+
+    return -1;
+}
+
+#define ERROR_OUT(err) \
+    { cue->error = err; return err; }
+
+int cue_parse_keyword(psxd_cue_t* cue) {
+    if (!isalpha(cue->c))
+        ERROR_OUT(PE_EXPECTED_KEYWORD);
+    
+    while (isalnum(cue->c) || (cue->c == '/')) {
+        *cue->ptr++ = cue->c;
+
+        cue->c = fgetc(cue->file);
+    }
+
+    *cue->ptr = 0;
+
+    cue->ptr = cue->buf;
+
+    cue_ignore_whitespace(cue);
+
+    return 0;
+}
+
+int cue_parse_string(psxd_cue_t* cue) {
+    if (cue->c != '\"')
+        ERROR_OUT(PE_EXPECTED_STRING);
+
+    cue->c = fgetc(cue->file);
+
+    while (cue->c != '\"') {
+        *cue->ptr++ = cue->c;
+
+        cue->c = fgetc(cue->file);
+    }
+
+    *cue->ptr = 0;
+
+    cue->c = fgetc(cue->file);
+
+    cue->ptr = cue->buf;
+
+    cue_ignore_whitespace(cue);
+
+    return 0;
+}
+
+int cue_parse_number(psxd_cue_t* cue) {
+    if (!isdigit(cue->c))
+        ERROR_OUT(PE_EXPECTED_NUMBER);
+    
+    while (isdigit(cue->c)) {
+        *cue->ptr++ = cue->c;
+
+        cue->c = fgetc(cue->file);
+    }
+
+    *cue->ptr = 0;
+
+    cue->ptr = cue->buf;
+
+    cue_ignore_whitespace(cue);
+
+    return 0;
+}
+
+int cue_parse_msf(psxd_cue_t* cue, msf_t* msf) {
+    if (cue_parse_number(cue))
+        return cue->error;
+    
+    if (cue->c != ':')
+        ERROR_OUT(PE_EXPECTED_COLON);
+    
+    cue->c = fgetc(cue->file);
+
+    msf->m = atoi(cue->buf);
+
+    if (cue_parse_number(cue))
+        return cue->error;
+
+    if (cue->c != ':')
+        ERROR_OUT(PE_EXPECTED_COLON);
+    
+    cue->c = fgetc(cue->file);
+    
+    msf->s = atoi(cue->buf);
+
+    if (cue_parse_number(cue))
+        return cue->error;
+    
+    msf->f = atoi(cue->buf);
+
+    return 0;
+}
+
+int cue_parse(psxd_cue_t* cue, FILE* file) {
+    cue->file = file;
+    cue->c = fgetc(file);
+
+    msf_t msf;
+
+    EXPECT_KEYWORD(CUE_FILE);
+
+    parse_file:
+
+    if (cue_parse_string(cue))
+        return cue->error;
+
+    strcpy(cue->current_file, cue->buf);
+
+    EXPECT_KEYWORD(CUE_BINARY);
+    EXPECT_KEYWORD(CUE_TRACK);
+
+    parse_track:
+
+    if (cue_parse_number(cue))
+        return cue->error;
+    
+    int track = atoi(cue->buf) - 1;
+
+    if (track != cue->num_tracks)
+        ERROR_OUT(PE_NON_SEQUENTIAL_TRACKS);
+    
+    cue_add_track(cue);
+
+    cue->track[track]->filename = malloc(strlen(cue->current_file) + 1);
+
+    // Copy current file to track filename
+    strcpy(cue->track[track]->filename, cue->current_file);
+
+    if (cue_parse_keyword(cue))
+        return cue->error;
+
+    cue->track[track]->type = cue_get_keyword(cue);
+
+    // Expecting at least 1 index
+    EXPECT_KEYWORD(CUE_INDEX);
+
+    parse_index:
+
+    if (cue_parse_number(cue))
+        return cue->error;
+    
+    int index = atoi(cue->buf);
+
+    if (cue_parse_msf(cue, &msf))
+        return cue->error;
+
+    cue->track[track]->index[index] = msf;
+
+    if (feof(cue->file)) {
+        fclose(file);
+
+        return 0;
+    }
+
+    if (cue_parse_keyword(cue))
+        return cue->error;
+    
+    switch (cue_get_keyword(cue)) {
+        case CUE_INDEX:
+            goto parse_index;
+
+        case CUE_FILE:
+            goto parse_file;
+
+        case CUE_TRACK:
+            goto parse_track;
+
+        default:
+            ERROR_OUT(PE_UNEXPECTED_TOKEN);
+    }
+}
+
+psxd_cue_t* psxd_cue_create() {
+    return (psxd_cue_t*)malloc(sizeof(psxd_cue_t));
+}
+
+void psxd_cue_init(psxd_cue_t* cue) {
+    memset(cue, 0, sizeof(psxd_cue_t));
+
+    cue->buf = malloc(CUE_BUF_SIZE);
+    cue->ptr = cue->buf;
+    cue->current_file = malloc(CUE_BUF_SIZE);
+}
+
+char* cue_get_directory(const char* path) {
+    const char* ptr = &path[strlen(path) - 1];
+    char* dir = NULL;
+
+    while ((*ptr != '/') && (*ptr != '\\') && (ptr != path))
+        ptr--;
+    
+    // If no directory specified, assume CWD
+    if (ptr == path) {
+        dir = malloc(3);
+
+        strcpy(dir, ".\\");
+
+        return dir;
+    }
+    
+    ptrdiff_t len = (ptr - path) + 1;
+
+    dir = malloc(len + 1);
+
+    strncpy(dir, path, len);
+
+    dir[len] = 0;
+
+    return dir;
+}
+
+int psxd_cue_load(psxd_cue_t* cue, const char* path) {
+    FILE* file = fopen(path, "rb");
+
+    if (!file) {
+        log_fatal("Couldn't open file \'%s\'", path);
+
+        return 1;
+    }
+
+    log_fatal("Parsing CUE...");
+
+    if (cue_parse(cue, file)) {
+        log_fatal("CUE error %s (%u)",
+            g_psxd_cue_errors[cue->error],
+            cue->error
+        );
+
+        exit(1);
+    }
+
+    log_fatal("Loading CD image...");
+
+    size_t offset = 0;
+
+    char* directory = cue_get_directory(path);
+    size_t directory_len = strlen(directory);
+
+    for (int i = 0; i < cue->num_tracks; i++) {
+        cue_track_t* track = cue->track[i];
+
+        int len = strlen(track->filename) + directory_len;
+
+        char* full_path = malloc(len + 2);
+
+        strcpy(full_path, directory);
+        strcat(full_path, track->filename);
+
+        log_fatal("Loading track %u at \'%s\'...", i + 1, full_path);
+
+        FILE* track_file = fopen(full_path, "rb");
+
+        if (ferror(track_file) || !track_file) {
+            fclose(track_file);
+
+            return 1;
+        }
+
+        uint32_t data_offset = msf_to_address(track->index[1]);
+
+        // Get track size
+        fseek(track_file, 0, SEEK_END);
+
+        // Account for index 1 offset
+        track->size = ftell(track_file);
+
+        cue->buf_size += track->size;
+
+        // Calculate track MS(F)
+        msf_from_address(&track->disc_offset, offset + data_offset);
+        msf_add_s(&track->disc_offset, 2);
+
+        cue->buf = cue_alloc_block(cue->buf, &offset, track->size);
+
+        fseek(track_file, 0, SEEK_SET);
+        
+        if (!fread(cue->buf + (offset - track->size), 1, track->size, track_file)) {
+            perror("Error reading CUE image file data");
+
+            exit(1);
+        }
+
+        fclose(track_file);
+        free(full_path);
+    }
+
+    // Calculate disc end MSF
+    msf_from_address(&cue->end, offset);
+    msf_add_s(&cue->end, 2);
+
+    free(directory);
+
+    log_fatal("Loaded CUE image, size=%08x, end=%02u:%02u:%02u",
+        cue->buf_size,
+        cue->end.m,
+        cue->end.s,
+        cue->end.f
+    );
+
+    return 0;
+}
+
+int psxd_cue_seek(void* udata, msf_t msf) {
+    psxd_cue_t* cue = udata;
+
+    // To-do: Check for OOB seeks
+
+    uint32_t sectors = (((msf.m * 60) + msf.s - 2) * CD_SECTORS_PS) + msf.f;
+
+    cue->seek_offset = sectors * CD_SECTOR_SIZE;
+
+    // log_fatal("CUE seek to %02u:%02u:%02u (%08x < %08x)", msf.m, msf.s, msf.f, cue->seek_offset, cue->buf_size);
+
+    if (cue->seek_offset >= cue->buf_size)
+        return DISC_ERR_ADDR_OUT_OF_BOUNDS;
+
+    return 0;
+}
+
+int psxd_cue_read_sector(void* udata, void* buf) {
+    psxd_cue_t* cue = udata;
+
+    log_fatal("Reading sector at offset %08x", cue->seek_offset);
+
+    memcpy(buf, cue->buf + cue->seek_offset, CD_SECTOR_SIZE);
+
+    return 0;
+}
+
+int psxd_cue_get_track_addr(void* udata, msf_t* msf, int track) {
+    psxd_cue_t* cue = udata;
+
+    track = BTOI(track);
+
+    if (track > cue->num_tracks)
+        return DISC_ERR_TRACK_OUT_OF_BOUNDS;
+
+    if (!msf)
+        return 0;
+
+    if (!track) {
+        msf->m = cue->end.m;
+        msf->s = cue->end.s;
+        msf->f = 0;
+
+        return 0;
+    }
+
+    msf->m = cue->track[track - 1]->disc_offset.m;
+    msf->s = cue->track[track - 1]->disc_offset.s;
+    msf->f = 0;
+
+    return 0;
+}
+
+int psxd_cue_get_track_count(void* udata, int* count) {
+    psxd_cue_t* cue = udata;
+
+    *count = ITOB(cue->num_tracks);
+
+    return 0;
+}
+
+void psxd_cue_init_disc(psxd_cue_t* cue, psx_disc_t* disc) {
+    disc->udata = cue;
+    disc->seek_func = psxd_cue_seek;
+    disc->read_sector_func = psxd_cue_read_sector;
+    disc->get_track_addr_func = psxd_cue_get_track_addr;
+    disc->get_track_count_func = psxd_cue_get_track_count;
+    disc->destroy_func = (disc_destroy_t)psxd_cue_destroy;
+}
+
+void psxd_cue_destroy(psxd_cue_t* cue) {
+    for (int i = 0; i < cue->num_tracks; i++) {
+        free(cue->track[i]->filename);
+        free(cue->track[i]);
+    }
+
+    free(cue->track);
+    free(cue->current_file);
+    free(cue->buf);
+    free(cue);
 }
\ No newline at end of file
--- a/psx/disc/cue.h
+++ b/psx/disc/cue.h
@@ -1,91 +1,91 @@
-/*
-    This file is part of the PSXE Emulator Project
-
-    CUE Parser + Loader
-*/
-
-#ifndef CUE_H
-#define CUE_H
-
-#include "../disc.h"
-#include "../msf.h"
-
-#include <stdlib.h>
-#include <stdint.h>
-#include <stdio.h>
-
-enum {
-    PE_EXPECTED_KEYWORD = 1,
-    PE_EXPECTED_STRING,
-    PE_EXPECTED_NUMBER,
-    PE_EXPECTED_COLON,
-    PE_NON_SEQUENTIAL_TRACKS,
-    PE_UNEXPECTED_TOKEN
-};
-
-enum {
-    CUE_4CH = 0,
-    CUE_AIFF,
-    CUE_AUDIO,
-    CUE_BINARY,
-    CUE_CATALOG,
-    CUE_CDG,
-    CUE_CDI_2336,
-    CUE_CDI_2352,
-    CUE_CDTEXTFILE,
-    CUE_DCP,
-    CUE_FILE,
-    CUE_FLAGS,
-    CUE_INDEX,
-    CUE_ISRC,
-    CUE_MODE1_2048,
-    CUE_MODE1_2352,
-    CUE_MODE2_2336,
-    CUE_MODE2_2352,
-    CUE_MOTOROLA,
-    CUE_MP3,
-    CUE_PERFORMER,
-    CUE_POSTGAP,
-    CUE_PRE,
-    CUE_PREGAP,
-    CUE_REM,
-    CUE_SCMS,
-    CUE_SONGWRITER,
-    CUE_TITLE,
-    CUE_TRACK,
-    CUE_WAVE,
-    CUE_NONE = 255
-};
-
-typedef struct {
-    char* filename;
-    int type;
-    void* buf;
-    msf_t index[2];
-    msf_t disc_offset;
-    size_t size;
-} cue_track_t;
-
-typedef struct {
-    int preload;
-    char* buf;
-    char* ptr;
-    char c;
-    int error;
-    FILE* file;
-    int num_tracks;
-    cue_track_t** track;
-    char* current_file;
-    char* directory;
-    uint32_t seek_offset;
-    uint32_t buf_size;
-    msf_t end;
-} psxd_cue_t;
-
-psxd_cue_t* psxd_cue_create();
-void psxd_cue_init(psxd_cue_t*);
-int psxd_cue_load(psxd_cue_t*, const char*);
-void psxd_cue_init_disc(psxd_cue_t*, psx_disc_t*);
-void psxd_cue_destroy(psxd_cue_t*);
-
+/*
+    This file is part of the PSXE Emulator Project
+
+    CUE Parser + Loader
+*/
+
+#ifndef CUE_H
+#define CUE_H
+
+#include "../disc.h"
+#include "../msf.h"
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+
+enum {
+    PE_EXPECTED_KEYWORD = 1,
+    PE_EXPECTED_STRING,
+    PE_EXPECTED_NUMBER,
+    PE_EXPECTED_COLON,
+    PE_NON_SEQUENTIAL_TRACKS,
+    PE_UNEXPECTED_TOKEN
+};
+
+enum {
+    CUE_4CH = 0,
+    CUE_AIFF,
+    CUE_AUDIO,
+    CUE_BINARY,
+    CUE_CATALOG,
+    CUE_CDG,
+    CUE_CDI_2336,
+    CUE_CDI_2352,
+    CUE_CDTEXTFILE,
+    CUE_DCP,
+    CUE_FILE,
+    CUE_FLAGS,
+    CUE_INDEX,
+    CUE_ISRC,
+    CUE_MODE1_2048,
+    CUE_MODE1_2352,
+    CUE_MODE2_2336,
+    CUE_MODE2_2352,
+    CUE_MOTOROLA,
+    CUE_MP3,
+    CUE_PERFORMER,
+    CUE_POSTGAP,
+    CUE_PRE,
+    CUE_PREGAP,
+    CUE_REM,
+    CUE_SCMS,
+    CUE_SONGWRITER,
+    CUE_TITLE,
+    CUE_TRACK,
+    CUE_WAVE,
+    CUE_NONE = 255
+};
+
+typedef struct {
+    char* filename;
+    int type;
+    void* buf;
+    msf_t index[2];
+    msf_t disc_offset;
+    size_t size;
+} cue_track_t;
+
+typedef struct {
+    int preload;
+    char* buf;
+    char* ptr;
+    char c;
+    int error;
+    FILE* file;
+    int num_tracks;
+    cue_track_t** track;
+    char* current_file;
+    char* directory;
+    uint32_t seek_offset;
+    uint32_t buf_size;
+    msf_t end;
+} psxd_cue_t;
+
+psxd_cue_t* psxd_cue_create();
+void psxd_cue_init(psxd_cue_t*);
+int psxd_cue_load(psxd_cue_t*, const char*);
+void psxd_cue_init_disc(psxd_cue_t*, psx_disc_t*);
+void psxd_cue_destroy(psxd_cue_t*);
+
 #endif
\ No newline at end of file
--- a/psx/exe.c
+++ b/psx/exe.c
@@ -1,50 +1,50 @@
-#include <stdio.h>
-
-#include "exe.h"
-#include "log.h"
-
-void psx_exe_load(psx_cpu_t* cpu, const char* path) {
-    FILE* file = fopen(path, "rb");
-
-    if (!file) {
-        log_error("Couldn't open PS-X EXE file \"%s\"", path);
-
-        exit(1);
-    }
-
-    // Read header
-    psx_exe_hdr_t hdr;
-    
-    if (!fread((char*)&hdr, 1, sizeof(psx_exe_hdr_t), file)) {
-        perror("Error reading PS-EXE header");
-
-        exit(1);
-    }
-
-    // Seek to program start 
-    fseek(file, 0x800, SEEK_SET);
-
-    // Read to RAM directly
-    uint32_t offset = hdr.ramdest & 0x7fffffff;
-
-    if (!fread(cpu->bus->ram->buf + offset, 1, hdr.filesz, file)) {
-        perror("Error reading PS-EXE data");
-
-        exit(1);
-    }
-
-    // Load initial register values
-    cpu->pc = hdr.ipc;
-    cpu->next_pc = cpu->pc + 4;
-    cpu->r[28] = hdr.igp;
-
-    if (hdr.ispb) {
-        cpu->r[29] = hdr.ispb + hdr.ispoff;
-        cpu->r[30] = cpu->r[29];
-    }
-
-    log_info("Loaded PS-X EXE file \"%s\"", path);
-    log_fatal("PC=%08x SP=%08x (%08x) GP=%08x", cpu->pc, cpu->r[29], hdr.ispb, cpu->r[28]);
-
-    fclose(file);
+#include <stdio.h>
+
+#include "exe.h"
+#include "log.h"
+
+void psx_exe_load(psx_cpu_t* cpu, const char* path) {
+    FILE* file = fopen(path, "rb");
+
+    if (!file) {
+        log_error("Couldn't open PS-X EXE file \"%s\"", path);
+
+        exit(1);
+    }
+
+    // Read header
+    psx_exe_hdr_t hdr;
+    
+    if (!fread((char*)&hdr, 1, sizeof(psx_exe_hdr_t), file)) {
+        perror("Error reading PS-EXE header");
+
+        exit(1);
+    }
+
+    // Seek to program start 
+    fseek(file, 0x800, SEEK_SET);
+
+    // Read to RAM directly
+    uint32_t offset = hdr.ramdest & 0x7fffffff;
+
+    if (!fread(cpu->bus->ram->buf + offset, 1, hdr.filesz, file)) {
+        perror("Error reading PS-EXE data");
+
+        exit(1);
+    }
+
+    // Load initial register values
+    cpu->pc = hdr.ipc;
+    cpu->next_pc = cpu->pc + 4;
+    cpu->r[28] = hdr.igp;
+
+    if (hdr.ispb) {
+        cpu->r[29] = hdr.ispb + hdr.ispoff;
+        cpu->r[30] = cpu->r[29];
+    }
+
+    log_info("Loaded PS-X EXE file \"%s\"", path);
+    log_fatal("PC=%08x SP=%08x (%08x) GP=%08x", cpu->pc, cpu->r[29], hdr.ispb, cpu->r[28]);
+
+    fclose(file);
 }
\ No newline at end of file
--- a/psx/exe.h
+++ b/psx/exe.h
@@ -1,53 +1,53 @@
-#ifndef EXE_H
-#define EXE_H
-
-#include <stdint.h>
-#include <stdlib.h>
-#include <stdio.h>
-
-#include "cpu.h"
-#include "bus_init.h"
-
-/*
-PSX executables are having an 800h-byte header, followed by the code/data.
-
-  000h-007h ASCII ID "PS-X EXE"
-  008h-00Fh Zerofilled
-  010h      Initial PC                   (usually 80010000h, or higher)
-  014h      Initial GP/R28               (usually 0)
-  018h      Destination Address in RAM   (usually 80010000h, or higher)
-  01Ch      Filesize (must be N*800h)    (excluding 800h-byte header)
-  020h      Unknown/Unused ;Addr         (usually 0)  ;\optional overlay?
-  024h      Unknown/Unused ;Size         (usually 0)  ;/(not auto-loaded)
-  028h      Memfill Start Address        (usually 0) (when below Size=None)
-  02Ch      Memfill Size in bytes        (usually 0) (0=None)
-  030h      Initial SP/R29 & FP/R30 Base (usually 801FFFF0h) (or 0=None)
-  034h      Initial SP/R29 & FP/R30 Offs (usually 0, added to above Base)
-  038h-04Bh Reserved for A(43h) Function (should be zerofilled in exefile)
-  04Ch-xxxh ASCII marker
-             "Sony Computer Entertainment Inc. for Japan area"         ;NTSC
-             "Sony Computer Entertainment Inc. for Europe area"        ;PAL
-             "Sony Computer Entertainment Inc. for North America area" ;NTSC
-             (or often zerofilled in some homebrew files)
-             (the BIOS doesn't verify this string, and boots fine without it)
-  xxxh-7FFh Zerofilled
-  800h...   Code/Data                  (loaded to entry[018h] and up)
-*/
-
-typedef struct {
-    char id[16];
-    uint32_t ipc;
-    uint32_t igp;
-    uint32_t ramdest;
-    uint32_t filesz;
-    uint32_t unk20;
-    uint32_t unk24;
-    uint32_t mfill_start;
-    uint32_t mfill_size;
-    uint32_t ispb;
-    uint32_t ispoff;
-} psx_exe_hdr_t;
-
-void psx_exe_load(psx_cpu_t*, const char*);
-
+#ifndef EXE_H
+#define EXE_H
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "cpu.h"
+#include "bus_init.h"
+
+/*
+PSX executables are having an 800h-byte header, followed by the code/data.
+
+  000h-007h ASCII ID "PS-X EXE"
+  008h-00Fh Zerofilled
+  010h      Initial PC                   (usually 80010000h, or higher)
+  014h      Initial GP/R28               (usually 0)
+  018h      Destination Address in RAM   (usually 80010000h, or higher)
+  01Ch      Filesize (must be N*800h)    (excluding 800h-byte header)
+  020h      Unknown/Unused ;Addr         (usually 0)  ;\optional overlay?
+  024h      Unknown/Unused ;Size         (usually 0)  ;/(not auto-loaded)
+  028h      Memfill Start Address        (usually 0) (when below Size=None)
+  02Ch      Memfill Size in bytes        (usually 0) (0=None)
+  030h      Initial SP/R29 & FP/R30 Base (usually 801FFFF0h) (or 0=None)
+  034h      Initial SP/R29 & FP/R30 Offs (usually 0, added to above Base)
+  038h-04Bh Reserved for A(43h) Function (should be zerofilled in exefile)
+  04Ch-xxxh ASCII marker
+             "Sony Computer Entertainment Inc. for Japan area"         ;NTSC
+             "Sony Computer Entertainment Inc. for Europe area"        ;PAL
+             "Sony Computer Entertainment Inc. for North America area" ;NTSC
+             (or often zerofilled in some homebrew files)
+             (the BIOS doesn't verify this string, and boots fine without it)
+  xxxh-7FFh Zerofilled
+  800h...   Code/Data                  (loaded to entry[018h] and up)
+*/
+
+typedef struct {
+    char id[16];
+    uint32_t ipc;
+    uint32_t igp;
+    uint32_t ramdest;
+    uint32_t filesz;
+    uint32_t unk20;
+    uint32_t unk24;
+    uint32_t mfill_start;
+    uint32_t mfill_size;
+    uint32_t ispb;
+    uint32_t ispoff;
+} psx_exe_hdr_t;
+
+void psx_exe_load(psx_cpu_t*, const char*);
+
 #endif
\ No newline at end of file
--- a/psx/input/sda.c
+++ b/psx/input/sda.c
@@ -1,93 +1,93 @@
-/*
-    This file is part of the PSXE Emulator Project
-
-    Sony PlayStation Standard Digital/Analog Controller Emulator
-*/
-
-#include "sda.h"
-#include "../log.h"
-
-#include <stdlib.h>
-#include <string.h>
-
-psxi_sda_t* psxi_sda_create() {
-    return (psxi_sda_t*)malloc(sizeof(psxi_sda_t));
-}
-
-void psxi_sda_init(psxi_sda_t* sda, uint16_t model) {
-    memset(sda, 0, sizeof(psxi_sda_t));
-
-    sda->tx_data = 0xff;
-    sda->tx_data_ready = 1;
-    sda->model = model;
-    sda->state = SDA_STATE_TX_HIZ;
-    sda->sw = 0xffff;
-}
-
-uint32_t psxi_sda_read(void* udata) {
-    psxi_sda_t* sda = (psxi_sda_t*)udata;
-
-    switch (sda->state) {
-        case SDA_STATE_TX_HIZ: sda->tx_data = 0xff; break;
-        case SDA_STATE_TX_IDL: sda->tx_data = sda->model; break;
-        case SDA_STATE_TX_IDH: sda->tx_data = 0x5a; break;
-        case SDA_STATE_TX_SWL: sda->tx_data = sda->sw & 0xff; break;
-
-        // Last state
-        case SDA_STATE_TX_SWH: {
-            sda->tx_data_ready = 0;
-            sda->state = 0;
-
-            return sda->sw >> 8;
-        } break;
-    }
-
-    sda->tx_data_ready = 1;
-    sda->state++;
-
-    return sda->tx_data;
-}
-
-void psxi_sda_write(void* udata, uint16_t data) {
-    // To-do: Handle TAP and MOT bytes here
-
-    return;
-}
-
-void psxi_sda_on_button_press(void* udata, uint16_t data) {
-    psxi_sda_t* sda = (psxi_sda_t*)udata;
-
-    sda->sw &= ~data;
-}
-
-void psxi_sda_on_button_release(void* udata, uint16_t data) {
-    psxi_sda_t* sda = (psxi_sda_t*)udata;
-
-    sda->sw |= data;
-}
-
-// To-do: Implement analog mode
-void psxi_sda_on_analog_change(void* udata, uint16_t data) {
-    // Suppress warning until we implement analog mode
-    // psxi_sda_t* sda = (psxi_sda_t*)udata;
-}
-
-int psxi_sda_query_fifo(void* udata) {
-    psxi_sda_t* sda = (psxi_sda_t*)udata;
-
-    return sda->tx_data_ready;
-}
-
-void psxi_sda_init_input(psxi_sda_t* sda, psx_input_t* input) {
-    input->udata = sda;
-    input->write_func = psxi_sda_write;
-    input->read_func = psxi_sda_read;
-    input->on_button_press_func = psxi_sda_on_button_press;
-    input->on_button_release_func = psxi_sda_on_button_release;
-    input->on_analog_change_func = psxi_sda_on_analog_change;
-    input->query_fifo_func = psxi_sda_query_fifo;
-}
-
-void psxi_sda_destroy(psxi_sda_t* sda) {
-    free(sda);
+/*
+    This file is part of the PSXE Emulator Project
+
+    Sony PlayStation Standard Digital/Analog Controller Emulator
+*/
+
+#include "sda.h"
+#include "../log.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+psxi_sda_t* psxi_sda_create() {
+    return (psxi_sda_t*)malloc(sizeof(psxi_sda_t));
+}
+
+void psxi_sda_init(psxi_sda_t* sda, uint16_t model) {
+    memset(sda, 0, sizeof(psxi_sda_t));
+
+    sda->tx_data = 0xff;
+    sda->tx_data_ready = 1;
+    sda->model = model;
+    sda->state = SDA_STATE_TX_HIZ;
+    sda->sw = 0xffff;
+}
+
+uint32_t psxi_sda_read(void* udata) {
+    psxi_sda_t* sda = (psxi_sda_t*)udata;
+
+    switch (sda->state) {
+        case SDA_STATE_TX_HIZ: sda->tx_data = 0xff; break;
+        case SDA_STATE_TX_IDL: sda->tx_data = sda->model; break;
+        case SDA_STATE_TX_IDH: sda->tx_data = 0x5a; break;
+        case SDA_STATE_TX_SWL: sda->tx_data = sda->sw & 0xff; break;
+
+        // Last state
+        case SDA_STATE_TX_SWH: {
+            sda->tx_data_ready = 0;
+            sda->state = 0;
+
+            return sda->sw >> 8;
+        } break;
+    }
+
+    sda->tx_data_ready = 1;
+    sda->state++;
+
+    return sda->tx_data;
+}
+
+void psxi_sda_write(void* udata, uint16_t data) {
+    // To-do: Handle TAP and MOT bytes here
+
+    return;
+}
+
+void psxi_sda_on_button_press(void* udata, uint16_t data) {
+    psxi_sda_t* sda = (psxi_sda_t*)udata;
+
+    sda->sw &= ~data;
+}
+
+void psxi_sda_on_button_release(void* udata, uint16_t data) {
+    psxi_sda_t* sda = (psxi_sda_t*)udata;
+
+    sda->sw |= data;
+}
+
+// To-do: Implement analog mode
+void psxi_sda_on_analog_change(void* udata, uint16_t data) {
+    // Suppress warning until we implement analog mode
+    // psxi_sda_t* sda = (psxi_sda_t*)udata;
+}
+
+int psxi_sda_query_fifo(void* udata) {
+    psxi_sda_t* sda = (psxi_sda_t*)udata;
+
+    return sda->tx_data_ready;
+}
+
+void psxi_sda_init_input(psxi_sda_t* sda, psx_input_t* input) {
+    input->udata = sda;
+    input->write_func = psxi_sda_write;
+    input->read_func = psxi_sda_read;
+    input->on_button_press_func = psxi_sda_on_button_press;
+    input->on_button_release_func = psxi_sda_on_button_release;
+    input->on_analog_change_func = psxi_sda_on_analog_change;
+    input->query_fifo_func = psxi_sda_query_fifo;
+}
+
+void psxi_sda_destroy(psxi_sda_t* sda) {
+    free(sda);
 }
\ No newline at end of file
--- a/psx/input/sda.h
+++ b/psx/input/sda.h
@@ -1,61 +1,61 @@
-/*
-    This file is part of the PSXE Emulator Project
-
-    Sony PlayStation Standard Digital/Analog Controller Emulator
-*/
-
-#ifndef SDA_H
-#define SDA_H
-
-#include "../dev/input.h"
-
-// Controller/Input IDs
-#define SDA_MODEL_DIGITAL       0x41
-#define SDA_MODEL_ANALOG_PAD    0x73
-#define SDA_MODEL_ANALOG_STICK  0x53
-
-#define PSXI_SW_SDA_SELECT      0x0001
-#define PSXI_SW_SDA_L3          0x0002
-#define PSXI_SW_SDA_R3          0x0004
-#define PSXI_SW_SDA_START       0x0008
-#define PSXI_SW_SDA_PAD_UP      0x0010
-#define PSXI_SW_SDA_PAD_RIGHT   0x0020
-#define PSXI_SW_SDA_PAD_DOWN    0x0040
-#define PSXI_SW_SDA_PAD_LEFT    0x0080
-#define PSXI_SW_SDA_L2          0x0100
-#define PSXI_SW_SDA_R2          0x0200
-#define PSXI_SW_SDA_L1          0x0400
-#define PSXI_SW_SDA_R1          0x0800
-#define PSXI_SW_SDA_TRIANGLE    0x1000
-#define PSXI_SW_SDA_CIRCLE      0x2000
-#define PSXI_SW_SDA_CROSS       0x4000
-#define PSXI_SW_SDA_SQUARE      0x8000
-
-enum {
-    SDA_STATE_TX_HIZ = 0,
-    SDA_STATE_TX_IDL,
-    SDA_STATE_TX_IDH,
-    SDA_STATE_TX_SWL,
-    SDA_STATE_TX_SWH
-};
-
-enum {
-    SA_MODE_DIGITAL = 0,
-    SA_MODE_ANALOG
-};
-
-typedef struct {
-    uint8_t model;
-    int state;
-    int sa_mode;
-    uint16_t sw;
-    uint8_t tx_data;
-    int tx_data_ready;
-} psxi_sda_t;
-
-psxi_sda_t* psxi_sda_create();
-void psxi_sda_init(psxi_sda_t*, uint16_t);
-void psxi_sda_init_input(psxi_sda_t*, psx_input_t*);
-void psxi_sda_destroy(psxi_sda_t*);
-
+/*
+    This file is part of the PSXE Emulator Project
+
+    Sony PlayStation Standard Digital/Analog Controller Emulator
+*/
+
+#ifndef SDA_H
+#define SDA_H
+
+#include "../dev/input.h"
+
+// Controller/Input IDs
+#define SDA_MODEL_DIGITAL       0x41
+#define SDA_MODEL_ANALOG_PAD    0x73
+#define SDA_MODEL_ANALOG_STICK  0x53
+
+#define PSXI_SW_SDA_SELECT      0x0001
+#define PSXI_SW_SDA_L3          0x0002
+#define PSXI_SW_SDA_R3          0x0004
+#define PSXI_SW_SDA_START       0x0008
+#define PSXI_SW_SDA_PAD_UP      0x0010
+#define PSXI_SW_SDA_PAD_RIGHT   0x0020
+#define PSXI_SW_SDA_PAD_DOWN    0x0040
+#define PSXI_SW_SDA_PAD_LEFT    0x0080
+#define PSXI_SW_SDA_L2          0x0100
+#define PSXI_SW_SDA_R2          0x0200
+#define PSXI_SW_SDA_L1          0x0400
+#define PSXI_SW_SDA_R1          0x0800
+#define PSXI_SW_SDA_TRIANGLE    0x1000
+#define PSXI_SW_SDA_CIRCLE      0x2000
+#define PSXI_SW_SDA_CROSS       0x4000
+#define PSXI_SW_SDA_SQUARE      0x8000
+
+enum {
+    SDA_STATE_TX_HIZ = 0,
+    SDA_STATE_TX_IDL,
+    SDA_STATE_TX_IDH,
+    SDA_STATE_TX_SWL,
+    SDA_STATE_TX_SWH
+};
+
+enum {
+    SA_MODE_DIGITAL = 0,
+    SA_MODE_ANALOG
+};
+
+typedef struct {
+    uint8_t model;
+    int state;
+    int sa_mode;
+    uint16_t sw;
+    uint8_t tx_data;
+    int tx_data_ready;
+} psxi_sda_t;
+
+psxi_sda_t* psxi_sda_create();
+void psxi_sda_init(psxi_sda_t*, uint16_t);
+void psxi_sda_init_input(psxi_sda_t*, psx_input_t*);
+void psxi_sda_destroy(psxi_sda_t*);
+
 #endif
\ No newline at end of file
--- a/psx/msf.c
+++ b/psx/msf.c
@@ -1,86 +1,86 @@
-#include "msf.h"
-#include "log.h"
-
-uint8_t msf_btoi(uint8_t b) {
-    return ((b >> 4) * 10) + (b & 0xf);
-}
-
-uint8_t msf_itob(int i) {
-    return i + (6 * (i / 10));
-}
-
-void msf_copy(msf_t* dst, msf_t src) {
-    dst->m = src.m;
-    dst->s = src.s;
-    dst->f = src.f;
-}
-
-void msf_adjust(msf_t* msf) {
-    if (msf->f > 75) {
-        int s = msf->f / CD_SECTORS_PS;
-
-        msf->s += s;
-        msf->f -= CD_SECTORS_PS * s;
-    }
-
-    if (msf->s > 60) {
-        int m = msf->s / 60;
-
-        msf->m += m;
-        msf->s -= 60 * m;
-    }
-}
-
-void msf_adjust_sub(msf_t* msf) {
-    if ((int)msf->f < 0) {
-        int f = ((int)msf->f) * -1;
-        int s = (f / 60) + 1;
-
-        msf->s -= s;
-        msf->f += CD_SECTORS_PS * f;
-    }
-
-    if ((int)msf->s < 0) {
-        int s = ((int)msf->s) * -1;
-        int m = (s / 60) + 1;
-
-        msf->m -= m;
-        msf->s += 60 * m;
-    }
-}
-
-void msf_to_bcd(msf_t* msf) {
-    msf->m = ITOB(msf->m);
-    msf->s = ITOB(msf->s);
-    msf->f = ITOB(msf->f);
-}
-
-void msf_from_bcd(msf_t* msf) {
-    msf->m = BTOI(msf->m);
-    msf->s = BTOI(msf->s);
-    msf->f = BTOI(msf->f);
-}
-
-uint32_t msf_to_address(msf_t msf) {
-    return (((msf.m * 60) * CD_SECTORS_PS) + (msf.s * 75) + msf.f) * CD_SECTOR_SIZE;
-}
-
-void msf_from_address(msf_t* msf, uint32_t addr) {
-    msf->f = addr / CD_SECTOR_SIZE;
-    msf->s = msf->f / CD_SECTORS_PS;
-    msf->m = msf->s / 60;
-    msf->s -= msf->m * 60;
-    msf->f -= ((msf->m * 60) + msf->s) * CD_SECTORS_PS;
-}
-
-void msf_add_f(msf_t* msf, int f) {
-    msf->f += f;
-
-    msf_adjust(msf);
-}
-
-void msf_add_s(msf_t* msf, int s) {
-    msf->s += s;
-
-    msf_adjust(msf);
+#include "msf.h"
+#include "log.h"
+
+uint8_t msf_btoi(uint8_t b) {
+    return ((b >> 4) * 10) + (b & 0xf);
+}
+
+uint8_t msf_itob(int i) {
+    return i + (6 * (i / 10));
+}
+
+void msf_copy(msf_t* dst, msf_t src) {
+    dst->m = src.m;
+    dst->s = src.s;
+    dst->f = src.f;
+}
+
+void msf_adjust(msf_t* msf) {
+    if (msf->f > 75) {
+        int s = msf->f / CD_SECTORS_PS;
+
+        msf->s += s;
+        msf->f -= CD_SECTORS_PS * s;
+    }
+
+    if (msf->s > 60) {
+        int m = msf->s / 60;
+
+        msf->m += m;
+        msf->s -= 60 * m;
+    }
+}
+
+void msf_adjust_sub(msf_t* msf) {
+    if ((int)msf->f < 0) {
+        int f = ((int)msf->f) * -1;
+        int s = (f / 60) + 1;
+
+        msf->s -= s;
+        msf->f += CD_SECTORS_PS * f;
+    }
+
+    if ((int)msf->s < 0) {
+        int s = ((int)msf->s) * -1;
+        int m = (s / 60) + 1;
+
+        msf->m -= m;
+        msf->s += 60 * m;
+    }
+}
+
+void msf_to_bcd(msf_t* msf) {
+    msf->m = ITOB(msf->m);
+    msf->s = ITOB(msf->s);
+    msf->f = ITOB(msf->f);
+}
+
+void msf_from_bcd(msf_t* msf) {
+    msf->m = BTOI(msf->m);
+    msf->s = BTOI(msf->s);
+    msf->f = BTOI(msf->f);
+}
+
+uint32_t msf_to_address(msf_t msf) {
+    return (((msf.m * 60) * CD_SECTORS_PS) + (msf.s * 75) + msf.f) * CD_SECTOR_SIZE;
+}
+
+void msf_from_address(msf_t* msf, uint32_t addr) {
+    msf->f = addr / CD_SECTOR_SIZE;
+    msf->s = msf->f / CD_SECTORS_PS;
+    msf->m = msf->s / 60;
+    msf->s -= msf->m * 60;
+    msf->f -= ((msf->m * 60) + msf->s) * CD_SECTORS_PS;
+}
+
+void msf_add_f(msf_t* msf, int f) {
+    msf->f += f;
+
+    msf_adjust(msf);
+}
+
+void msf_add_s(msf_t* msf, int s) {
+    msf->s += s;
+
+    msf_adjust(msf);
 }
\ No newline at end of file
--- a/psx/msf.h
+++ b/psx/msf.h
@@ -1,100 +1,100 @@
-#ifndef MSF_H
-#define MSF_H
-
-#include <stdint.h>
-
-#define CD_SECTOR_SIZE 0x930
-#define CD_SECTORS_PS 75
-
-// #define BTOI(b) msf_btoi(b)
-// #define ITOB(b) msf_itob(b)
-#define BTOI(b) g_btoi_table[b]
-#define ITOB(b) g_itob_table[b]
-
-static const uint8_t g_btoi_table[] = {
-    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
-    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
-    0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
-    0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
-    0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
-    0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
-    0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25,
-    0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d,
-    0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
-    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
-    0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
-    0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41,
-    0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43,
-    0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b,
-    0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d,
-    0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
-    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
-    0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
-    0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61,
-    0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
-    0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b,
-    0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73,
-    0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75,
-    0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d,
-    0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
-    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
-    0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
-    0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91,
-    0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93,
-    0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b,
-    0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d,
-    0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5,
-};
-
-static const uint8_t g_itob_table[] = {
-    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
-    0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
-    0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23,
-    0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31,
-    0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
-    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
-    0x48, 0x49, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
-    0x56, 0x57, 0x58, 0x59, 0x60, 0x61, 0x62, 0x63,
-    0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71,
-    0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
-    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
-    0x88, 0x89, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95,
-    0x96, 0x97, 0x98, 0x99, 0xa0, 0xa1, 0xa2, 0xa3,
-    0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xb0, 0xb1,
-    0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9,
-    0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
-    0xc8, 0xc9, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5,
-    0xd6, 0xd7, 0xd8, 0xd9, 0xe0, 0xe1, 0xe2, 0xe3,
-    0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xf0, 0xf1,
-    0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
-    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
-    0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
-    0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23,
-    0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31,
-    0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
-    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
-    0x48, 0x49, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
-    0x56, 0x57, 0x58, 0x59, 0x60, 0x61, 0x62, 0x63,
-    0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71,
-    0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
-    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
-    0x88, 0x89, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95,
-};
-
-typedef struct {
-    uint32_t m;
-    uint32_t s;
-    uint32_t f;
-} msf_t;
-
-void msf_copy(msf_t*, msf_t);
-void msf_adjust(msf_t*);
-void msf_adjust_sub(msf_t*);
-void msf_to_bcd(msf_t*);
-void msf_from_bcd(msf_t*);
-uint32_t msf_to_address(msf_t);
-void msf_from_address(msf_t*, uint32_t);
-void msf_add_f(msf_t*, int);
-void msf_add_s(msf_t*, int);
-
+#ifndef MSF_H
+#define MSF_H
+
+#include <stdint.h>
+
+#define CD_SECTOR_SIZE 0x930
+#define CD_SECTORS_PS 75
+
+// #define BTOI(b) msf_btoi(b)
+// #define ITOB(b) msf_itob(b)
+#define BTOI(b) g_btoi_table[b]
+#define ITOB(b) g_itob_table[b]
+
+static const uint8_t g_btoi_table[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
+    0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
+    0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+    0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+    0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25,
+    0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d,
+    0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+    0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+    0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41,
+    0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43,
+    0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b,
+    0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d,
+    0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
+    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+    0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+    0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61,
+    0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
+    0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b,
+    0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73,
+    0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75,
+    0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d,
+    0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
+    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+    0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
+    0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91,
+    0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93,
+    0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b,
+    0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d,
+    0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5,
+};
+
+static const uint8_t g_itob_table[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
+    0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23,
+    0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31,
+    0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+    0x48, 0x49, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
+    0x56, 0x57, 0x58, 0x59, 0x60, 0x61, 0x62, 0x63,
+    0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71,
+    0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
+    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+    0x88, 0x89, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95,
+    0x96, 0x97, 0x98, 0x99, 0xa0, 0xa1, 0xa2, 0xa3,
+    0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xb0, 0xb1,
+    0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9,
+    0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+    0xc8, 0xc9, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5,
+    0xd6, 0xd7, 0xd8, 0xd9, 0xe0, 0xe1, 0xe2, 0xe3,
+    0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xf0, 0xf1,
+    0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
+    0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23,
+    0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31,
+    0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+    0x48, 0x49, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
+    0x56, 0x57, 0x58, 0x59, 0x60, 0x61, 0x62, 0x63,
+    0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71,
+    0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
+    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+    0x88, 0x89, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95,
+};
+
+typedef struct {
+    uint32_t m;
+    uint32_t s;
+    uint32_t f;
+} msf_t;
+
+void msf_copy(msf_t*, msf_t);
+void msf_adjust(msf_t*);
+void msf_adjust_sub(msf_t*);
+void msf_to_bcd(msf_t*);
+void msf_from_bcd(msf_t*);
+uint32_t msf_to_address(msf_t);
+void msf_from_address(msf_t*, uint32_t);
+void msf_add_f(msf_t*, int);
+void msf_add_s(msf_t*, int);
+
 #endif
\ No newline at end of file
--- a/psx/psx.c
+++ b/psx/psx.c
@@ -1,299 +1,299 @@
-#include "psx/psx.h"
-
-psx_t* psx_create() {
-    return (psx_t*)malloc(sizeof(psx_t));
-}
-
-void psx_load_bios(psx_t* psx, const char* path) {
-    psx_bios_load(psx->bios, path);
-}
-
-void psx_load_state(psx_t* psx, const char* path) {
-    log_fatal("State saving/loading is not yet supported");
-
-    exit(1);
-}
-
-void psx_save_state(psx_t* psx, const char* path) {
-    log_fatal("State saving/loading is not yet supported");
-
-    exit(1);
-}
-
-void psx_load_exe(psx_t* psx, const char* path) {
-    psx_exe_load(psx->cpu, path);
-}
-
-void psx_update(psx_t* psx) {
-    psx_cpu_cycle(psx->cpu);
-
-    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);
-    psx_dma_update(psx->dma, psx->cpu->last_cycles);
-}
-
-void psx_run_frame(psx_t* psx) {
-    // NTSC: 59.29 Hz, PAL: 49.76 Hz
-    float framerate = (psx->gpu->display_mode & 0x8) ? 59.29 : 49.76;
-
-    unsigned int counter = (float)PSX_CPU_CPS / framerate;
-
-    while (counter--) {
-        psx_update(psx);
-    }
-}
-
-void* psx_get_display_buffer(psx_t* psx) {
-    return psx_gpu_get_display_buffer(psx->gpu);
-}
-
-void* psx_get_vram(psx_t* psx) {
-    return psx->gpu->vram;
-}
-
-uint32_t psx_get_display_width(psx_t* psx) {
-    // int draw = psx->gpu->draw_x2 - psx->gpu->draw_x1;
-    // int dmode = psx_get_dmode_width(psx);
-
-    // return (draw > dmode) ? dmode : draw;
-
-    return psx_get_dmode_width(psx);
-}
-
-uint32_t psx_get_display_height(psx_t* psx) {
-    // int draw = psx->gpu->draw_y2 - psx->gpu->draw_y1;
-    // int dmode = psx_get_dmode_height(psx);
-
-    // return (draw > dmode) ? dmode : draw;
-
-    return psx_get_dmode_height(psx);
-}
-
-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
-    };
-
-    if (psx->gpu->display_mode & 0x40) {
-        return 368;
-    } else {
-        return dmode_hres_table[psx->gpu->display_mode & 0x3];
-    }
-}
-
-uint32_t psx_get_dmode_height(psx_t* psx) {
-    return (psx->gpu->display_mode & 0x4) ? 480 : 240;
-}
-
-double psx_get_display_aspect(psx_t* psx) {
-    double width = psx_get_dmode_width(psx);
-    double height = psx_get_dmode_height(psx);
-
-    if (height > width)
-        return 4.0 / 3.0;
-
-    double aspect = width / height;
-
-    if (aspect > (4.0 / 3.0))
-        return 4.0 / 3.0;
-
-    return aspect;
-}
-
-void atcons_tx(void* udata, char c) {
-    putchar(c);
-}
-
-void psx_init(psx_t* psx, const char* bios_path, const char* exp_path) {
-    memset(psx, 0, sizeof(psx_t));
-
-    psx->bios = psx_bios_create();
-    psx->ram = psx_ram_create();
-    psx->dma = psx_dma_create();
-    psx->exp1 = psx_exp1_create();
-    psx->exp2 = psx_exp2_create();
-    psx->mc1 = psx_mc1_create();
-    psx->mc2 = psx_mc2_create();
-    psx->mc3 = psx_mc3_create();
-    psx->ic = psx_ic_create();
-    psx->scratchpad = psx_scratchpad_create();
-    psx->gpu = psx_gpu_create();
-    psx->spu = psx_spu_create();
-    psx->bus = psx_bus_create();
-    psx->cpu = psx_cpu_create();
-    psx->timer = psx_timer_create();
-    psx->cdrom = psx_cdrom_create();
-    psx->pad = psx_pad_create();
-    psx->mdec = psx_mdec_create();
-
-    psx_bus_init(psx->bus);
-
-    psx_bus_init_bios(psx->bus, psx->bios);
-    psx_bus_init_ram(psx->bus, psx->ram);
-    psx_bus_init_dma(psx->bus, psx->dma);
-    psx_bus_init_exp1(psx->bus, psx->exp1);
-    psx_bus_init_exp2(psx->bus, psx->exp2);
-    psx_bus_init_mc1(psx->bus, psx->mc1);
-    psx_bus_init_mc2(psx->bus, psx->mc2);
-    psx_bus_init_mc3(psx->bus, psx->mc3);
-    psx_bus_init_ic(psx->bus, psx->ic);
-    psx_bus_init_scratchpad(psx->bus, psx->scratchpad);
-    psx_bus_init_gpu(psx->bus, psx->gpu);
-    psx_bus_init_spu(psx->bus, psx->spu);
-    psx_bus_init_timer(psx->bus, psx->timer);
-    psx_bus_init_cdrom(psx->bus, psx->cdrom);
-    psx_bus_init_pad(psx->bus, psx->pad);
-    psx_bus_init_mdec(psx->bus, psx->mdec);
-
-    // Init devices
-    psx_bios_init(psx->bios);
-    psx_bios_load(psx->bios, bios_path);
-    psx_mc1_init(psx->mc1);
-    psx_mc2_init(psx->mc2);
-    psx_mc3_init(psx->mc3);
-    psx_ram_init(psx->ram, psx->mc2);
-    psx_dma_init(psx->dma, psx->bus, psx->ic);
-    psx_exp1_init(psx->exp1, psx->mc1, exp_path);
-    psx_exp2_init(psx->exp2, atcons_tx, NULL);
-    psx_ic_init(psx->ic, psx->cpu);
-    psx_scratchpad_init(psx->scratchpad);
-    psx_gpu_init(psx->gpu, psx->ic);
-    psx_spu_init(psx->spu);
-    psx_timer_init(psx->timer, psx->ic);
-    psx_cdrom_init(psx->cdrom, psx->ic);
-    psx_pad_init(psx->pad, psx->ic);
-    psx_mdec_init(psx->mdec);
-    psx_cpu_init(psx->cpu, psx->bus);
-}
-
-void psx_load_expansion(psx_t* psx, const char* path) {
-    psx_exp1_init(psx->exp1, psx->mc1, path);
-}
-
-void psx_hard_reset(psx_t* psx) {
-    log_fatal("Hard reset not yet implemented");
-
-    exit(1);
-}
-
-void psx_soft_reset(psx_t* psx) {
-    psx_cpu_init(psx->cpu, psx->bus);
-}
-
-uint32_t* psx_take_screenshot(psx_t* psx) {
-    log_fatal("Screenshots not yet supported");
-
-    exit(1);
-}
-
-void psx_swap_disc(psx_t* psx, const char* path) {
-    psx_cdrom_destroy(psx->cdrom);
-
-    psx->cdrom = psx_cdrom_create();
-
-    psx_bus_init_cdrom(psx->bus, psx->cdrom);
-
-    psx_cdrom_init(psx->cdrom, psx->ic);
-    psx_cdrom_open(psx->cdrom, path);
-}
-
-void psx_destroy(psx_t* psx) {
-    psx_cpu_destroy(psx->cpu);
-    psx_bios_destroy(psx->bios);
-    psx_bus_destroy(psx->bus);
-    psx_ram_destroy(psx->ram);
-    psx_exp1_destroy(psx->exp1);
-    psx_mc1_destroy(psx->mc1);
-    psx_mc2_destroy(psx->mc2);
-    psx_mc3_destroy(psx->mc3);
-    psx_ic_destroy(psx->ic);
-    psx_scratchpad_destroy(psx->scratchpad);
-    psx_gpu_destroy(psx->gpu);
-    psx_spu_destroy(psx->spu);
-    psx_timer_destroy(psx->timer);
-    psx_cdrom_destroy(psx->cdrom);
-    psx_pad_destroy(psx->pad);
-    psx_mdec_destroy(psx->mdec);
-
-    free(psx);
-}
-
-psx_bios_t* psx_get_bios(psx_t* psx) {
-    return psx->bios;
-}
-
-psx_ram_t* psx_get_ram(psx_t* psx) {
-    return psx->ram;
-}
-
-psx_dma_t* psx_get_dma(psx_t* psx) {
-    return psx->dma;
-}
-
-psx_exp1_t* psx_get_exp1(psx_t* psx) {
-    return psx->exp1;
-}
-
-psx_exp2_t* psx_get_exp2(psx_t* psx) {
-    return psx->exp2;
-}
-
-psx_mc1_t* psx_get_mc1(psx_t* psx) {
-    return psx->mc1;
-}
-
-psx_mc2_t* psx_get_mc2(psx_t* psx) {
-    return psx->mc2;
-}
-
-psx_mc3_t* psx_get_mc3(psx_t* psx) {
-    return psx->mc3;
-}
-
-psx_ic_t* psx_get_ic(psx_t* psx) {
-    return psx->ic;
-}
-
-psx_scratchpad_t* psx_get_scratchpad(psx_t* psx) {
-    return psx->scratchpad;
-}
-
-psx_gpu_t* psx_get_gpu(psx_t* psx) {
-    return psx->gpu;
-}
-
-psx_spu_t* psx_get_spu(psx_t* psx) {
-    return psx->spu;
-}
-
-psx_bus_t* psx_get_bus(psx_t* psx) {
-    return psx->bus;
-}
-
-psx_timer_t* psx_get_timer(psx_t* psx) {
-    return psx->timer;
-}
-
-psx_cdrom_t* psx_get_cdrom(psx_t* psx) {
-    return psx->cdrom;
-}
-
-psx_pad_t* psx_get_pad(psx_t* psx) {
-    return psx->pad;
-}
-
-psx_mdec_t* psx_get_mdec(psx_t* psx) {
-    return psx->mdec;
-}
-
-psx_cpu_t* psx_get_cpu(psx_t* psx) {
-    return psx->cpu;
+#include "psx/psx.h"
+
+psx_t* psx_create() {
+    return (psx_t*)malloc(sizeof(psx_t));
+}
+
+void psx_load_bios(psx_t* psx, const char* path) {
+    psx_bios_load(psx->bios, path);
+}
+
+void psx_load_state(psx_t* psx, const char* path) {
+    log_fatal("State saving/loading is not yet supported");
+
+    exit(1);
+}
+
+void psx_save_state(psx_t* psx, const char* path) {
+    log_fatal("State saving/loading is not yet supported");
+
+    exit(1);
+}
+
+void psx_load_exe(psx_t* psx, const char* path) {
+    psx_exe_load(psx->cpu, path);
+}
+
+void psx_update(psx_t* psx) {
+    psx_cpu_cycle(psx->cpu);
+
+    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);
+    psx_dma_update(psx->dma, psx->cpu->last_cycles);
+}
+
+void psx_run_frame(psx_t* psx) {
+    // NTSC: 59.29 Hz, PAL: 49.76 Hz
+    float framerate = (psx->gpu->display_mode & 0x8) ? 59.29 : 49.76;
+
+    unsigned int counter = (float)PSX_CPU_CPS / framerate;
+
+    while (counter--) {
+        psx_update(psx);
+    }
+}
+
+void* psx_get_display_buffer(psx_t* psx) {
+    return psx_gpu_get_display_buffer(psx->gpu);
+}
+
+void* psx_get_vram(psx_t* psx) {
+    return psx->gpu->vram;
+}
+
+uint32_t psx_get_display_width(psx_t* psx) {
+    // int draw = psx->gpu->draw_x2 - psx->gpu->draw_x1;
+    // int dmode = psx_get_dmode_width(psx);
+
+    // return (draw > dmode) ? dmode : draw;
+
+    return psx_get_dmode_width(psx);
+}
+
+uint32_t psx_get_display_height(psx_t* psx) {
+    // int draw = psx->gpu->draw_y2 - psx->gpu->draw_y1;
+    // int dmode = psx_get_dmode_height(psx);
+
+    // return (draw > dmode) ? dmode : draw;
+
+    return psx_get_dmode_height(psx);
+}
+
+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
+    };
+
+    if (psx->gpu->display_mode & 0x40) {
+        return 368;
+    } else {
+        return dmode_hres_table[psx->gpu->display_mode & 0x3];
+    }
+}
+
+uint32_t psx_get_dmode_height(psx_t* psx) {
+    return (psx->gpu->display_mode & 0x4) ? 480 : 240;
+}
+
+double psx_get_display_aspect(psx_t* psx) {
+    double width = psx_get_dmode_width(psx);
+    double height = psx_get_dmode_height(psx);
+
+    if (height > width)
+        return 4.0 / 3.0;
+
+    double aspect = width / height;
+
+    if (aspect > (4.0 / 3.0))
+        return 4.0 / 3.0;
+
+    return aspect;
+}
+
+void atcons_tx(void* udata, char c) {
+    putchar(c);
+}
+
+void psx_init(psx_t* psx, const char* bios_path, const char* exp_path) {
+    memset(psx, 0, sizeof(psx_t));
+
+    psx->bios = psx_bios_create();
+    psx->ram = psx_ram_create();
+    psx->dma = psx_dma_create();
+    psx->exp1 = psx_exp1_create();
+    psx->exp2 = psx_exp2_create();
+    psx->mc1 = psx_mc1_create();
+    psx->mc2 = psx_mc2_create();
+    psx->mc3 = psx_mc3_create();
+    psx->ic = psx_ic_create();
+    psx->scratchpad = psx_scratchpad_create();
+    psx->gpu = psx_gpu_create();
+    psx->spu = psx_spu_create();
+    psx->bus = psx_bus_create();
+    psx->cpu = psx_cpu_create();
+    psx->timer = psx_timer_create();
+    psx->cdrom = psx_cdrom_create();
+    psx->pad = psx_pad_create();
+    psx->mdec = psx_mdec_create();
+
+    psx_bus_init(psx->bus);
+
+    psx_bus_init_bios(psx->bus, psx->bios);
+    psx_bus_init_ram(psx->bus, psx->ram);
+    psx_bus_init_dma(psx->bus, psx->dma);
+    psx_bus_init_exp1(psx->bus, psx->exp1);
+    psx_bus_init_exp2(psx->bus, psx->exp2);
+    psx_bus_init_mc1(psx->bus, psx->mc1);
+    psx_bus_init_mc2(psx->bus, psx->mc2);
+    psx_bus_init_mc3(psx->bus, psx->mc3);
+    psx_bus_init_ic(psx->bus, psx->ic);
+    psx_bus_init_scratchpad(psx->bus, psx->scratchpad);
+    psx_bus_init_gpu(psx->bus, psx->gpu);
+    psx_bus_init_spu(psx->bus, psx->spu);
+    psx_bus_init_timer(psx->bus, psx->timer);
+    psx_bus_init_cdrom(psx->bus, psx->cdrom);
+    psx_bus_init_pad(psx->bus, psx->pad);
+    psx_bus_init_mdec(psx->bus, psx->mdec);
+
+    // Init devices
+    psx_bios_init(psx->bios);
+    psx_bios_load(psx->bios, bios_path);
+    psx_mc1_init(psx->mc1);
+    psx_mc2_init(psx->mc2);
+    psx_mc3_init(psx->mc3);
+    psx_ram_init(psx->ram, psx->mc2);
+    psx_dma_init(psx->dma, psx->bus, psx->ic);
+    psx_exp1_init(psx->exp1, psx->mc1, exp_path);
+    psx_exp2_init(psx->exp2, atcons_tx, NULL);
+    psx_ic_init(psx->ic, psx->cpu);
+    psx_scratchpad_init(psx->scratchpad);
+    psx_gpu_init(psx->gpu, psx->ic);
+    psx_spu_init(psx->spu);
+    psx_timer_init(psx->timer, psx->ic);
+    psx_cdrom_init(psx->cdrom, psx->ic);
+    psx_pad_init(psx->pad, psx->ic);
+    psx_mdec_init(psx->mdec);
+    psx_cpu_init(psx->cpu, psx->bus);
+}
+
+void psx_load_expansion(psx_t* psx, const char* path) {
+    psx_exp1_init(psx->exp1, psx->mc1, path);
+}
+
+void psx_hard_reset(psx_t* psx) {
+    log_fatal("Hard reset not yet implemented");
+
+    exit(1);
+}
+
+void psx_soft_reset(psx_t* psx) {
+    psx_cpu_init(psx->cpu, psx->bus);
+}
+
+uint32_t* psx_take_screenshot(psx_t* psx) {
+    log_fatal("Screenshots not yet supported");
+
+    exit(1);
+}
+
+void psx_swap_disc(psx_t* psx, const char* path) {
+    psx_cdrom_destroy(psx->cdrom);
+
+    psx->cdrom = psx_cdrom_create();
+
+    psx_bus_init_cdrom(psx->bus, psx->cdrom);
+
+    psx_cdrom_init(psx->cdrom, psx->ic);
+    psx_cdrom_open(psx->cdrom, path);
+}
+
+void psx_destroy(psx_t* psx) {
+    psx_cpu_destroy(psx->cpu);
+    psx_bios_destroy(psx->bios);
+    psx_bus_destroy(psx->bus);
+    psx_ram_destroy(psx->ram);
+    psx_exp1_destroy(psx->exp1);
+    psx_mc1_destroy(psx->mc1);
+    psx_mc2_destroy(psx->mc2);
+    psx_mc3_destroy(psx->mc3);
+    psx_ic_destroy(psx->ic);
+    psx_scratchpad_destroy(psx->scratchpad);
+    psx_gpu_destroy(psx->gpu);
+    psx_spu_destroy(psx->spu);
+    psx_timer_destroy(psx->timer);
+    psx_cdrom_destroy(psx->cdrom);
+    psx_pad_destroy(psx->pad);
+    psx_mdec_destroy(psx->mdec);
+
+    free(psx);
+}
+
+psx_bios_t* psx_get_bios(psx_t* psx) {
+    return psx->bios;
+}
+
+psx_ram_t* psx_get_ram(psx_t* psx) {
+    return psx->ram;
+}
+
+psx_dma_t* psx_get_dma(psx_t* psx) {
+    return psx->dma;
+}
+
+psx_exp1_t* psx_get_exp1(psx_t* psx) {
+    return psx->exp1;
+}
+
+psx_exp2_t* psx_get_exp2(psx_t* psx) {
+    return psx->exp2;
+}
+
+psx_mc1_t* psx_get_mc1(psx_t* psx) {
+    return psx->mc1;
+}
+
+psx_mc2_t* psx_get_mc2(psx_t* psx) {
+    return psx->mc2;
+}
+
+psx_mc3_t* psx_get_mc3(psx_t* psx) {
+    return psx->mc3;
+}
+
+psx_ic_t* psx_get_ic(psx_t* psx) {
+    return psx->ic;
+}
+
+psx_scratchpad_t* psx_get_scratchpad(psx_t* psx) {
+    return psx->scratchpad;
+}
+
+psx_gpu_t* psx_get_gpu(psx_t* psx) {
+    return psx->gpu;
+}
+
+psx_spu_t* psx_get_spu(psx_t* psx) {
+    return psx->spu;
+}
+
+psx_bus_t* psx_get_bus(psx_t* psx) {
+    return psx->bus;
+}
+
+psx_timer_t* psx_get_timer(psx_t* psx) {
+    return psx->timer;
+}
+
+psx_cdrom_t* psx_get_cdrom(psx_t* psx) {
+    return psx->cdrom;
+}
+
+psx_pad_t* psx_get_pad(psx_t* psx) {
+    return psx->pad;
+}
+
+psx_mdec_t* psx_get_mdec(psx_t* psx) {
+    return psx->mdec;
+}
+
+psx_cpu_t* psx_get_cpu(psx_t* psx) {
+    return psx->cpu;
 }
\ No newline at end of file
--- a/psx/psx.h
+++ b/psx/psx.h
@@ -1,72 +1,72 @@
-#ifndef PSX_H
-#define PSX_H
-
-#include "cpu.h"
-#include "log.h"
-#include "exe.h"
-
-#include <stdint.h>
-
-typedef struct {
-    psx_bios_t* bios;
-    psx_ram_t* ram;
-    psx_dma_t* dma;
-    psx_exp1_t* exp1;
-    psx_exp2_t* exp2;
-    psx_mc1_t* mc1;
-    psx_mc2_t* mc2;
-    psx_mc3_t* mc3;
-    psx_ic_t* ic;
-    psx_scratchpad_t* scratchpad;
-    psx_gpu_t* gpu;
-    psx_spu_t* spu;
-    psx_bus_t* bus;
-    psx_cpu_t* cpu;
-    psx_timer_t* timer;
-    psx_cdrom_t* cdrom;
-    psx_pad_t* pad;
-    psx_mdec_t* mdec;
-} psx_t;
-
-psx_t* psx_create();
-void psx_init(psx_t*, const char*, const char*);
-void psx_load_expansion(psx_t*, const char*);
-void psx_load_bios(psx_t*, const char*);
-void psx_hard_reset(psx_t*);
-void psx_soft_reset(psx_t*);
-void psx_load_state(psx_t*, const char*);
-void psx_save_state(psx_t*, const char*);
-void psx_load_exe(psx_t*, const char*);
-void psx_update(psx_t*);
-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*);
-void psx_swap_disc(psx_t*, const char*);
-psx_bios_t* psx_get_bios(psx_t*);
-psx_ram_t* psx_get_ram(psx_t*);
-psx_dma_t* psx_get_dma(psx_t*);
-psx_exp1_t* psx_get_exp1(psx_t*);
-psx_exp2_t* psx_get_exp2(psx_t*);
-psx_mc1_t* psx_get_mc1(psx_t*);
-psx_mc2_t* psx_get_mc2(psx_t*);
-psx_mc3_t* psx_get_mc3(psx_t*);
-psx_ic_t* psx_get_ic(psx_t*);
-psx_scratchpad_t* psx_get_scratchpad(psx_t*);
-psx_gpu_t* psx_get_gpu(psx_t*);
-psx_spu_t* psx_get_spu(psx_t*);
-psx_bus_t* psx_get_bus(psx_t*);
-psx_timer_t* psx_get_timer(psx_t*);
-psx_cdrom_t* psx_get_cdrom(psx_t*);
-psx_pad_t* psx_get_pad(psx_t*);
-psx_mdec_t* psx_get_mdec(psx_t*);
-psx_cpu_t* psx_get_cpu(psx_t*);
-void psx_destroy(psx_t*);
-
+#ifndef PSX_H
+#define PSX_H
+
+#include "cpu.h"
+#include "log.h"
+#include "exe.h"
+
+#include <stdint.h>
+
+typedef struct {
+    psx_bios_t* bios;
+    psx_ram_t* ram;
+    psx_dma_t* dma;
+    psx_exp1_t* exp1;
+    psx_exp2_t* exp2;
+    psx_mc1_t* mc1;
+    psx_mc2_t* mc2;
+    psx_mc3_t* mc3;
+    psx_ic_t* ic;
+    psx_scratchpad_t* scratchpad;
+    psx_gpu_t* gpu;
+    psx_spu_t* spu;
+    psx_bus_t* bus;
+    psx_cpu_t* cpu;
+    psx_timer_t* timer;
+    psx_cdrom_t* cdrom;
+    psx_pad_t* pad;
+    psx_mdec_t* mdec;
+} psx_t;
+
+psx_t* psx_create();
+void psx_init(psx_t*, const char*, const char*);
+void psx_load_expansion(psx_t*, const char*);
+void psx_load_bios(psx_t*, const char*);
+void psx_hard_reset(psx_t*);
+void psx_soft_reset(psx_t*);
+void psx_load_state(psx_t*, const char*);
+void psx_save_state(psx_t*, const char*);
+void psx_load_exe(psx_t*, const char*);
+void psx_update(psx_t*);
+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*);
+void psx_swap_disc(psx_t*, const char*);
+psx_bios_t* psx_get_bios(psx_t*);
+psx_ram_t* psx_get_ram(psx_t*);
+psx_dma_t* psx_get_dma(psx_t*);
+psx_exp1_t* psx_get_exp1(psx_t*);
+psx_exp2_t* psx_get_exp2(psx_t*);
+psx_mc1_t* psx_get_mc1(psx_t*);
+psx_mc2_t* psx_get_mc2(psx_t*);
+psx_mc3_t* psx_get_mc3(psx_t*);
+psx_ic_t* psx_get_ic(psx_t*);
+psx_scratchpad_t* psx_get_scratchpad(psx_t*);
+psx_gpu_t* psx_get_gpu(psx_t*);
+psx_spu_t* psx_get_spu(psx_t*);
+psx_bus_t* psx_get_bus(psx_t*);
+psx_timer_t* psx_get_timer(psx_t*);
+psx_cdrom_t* psx_get_cdrom(psx_t*);
+psx_pad_t* psx_get_pad(psx_t*);
+psx_mdec_t* psx_get_mdec(psx_t*);
+psx_cpu_t* psx_get_cpu(psx_t*);
+void psx_destroy(psx_t*);
+
 #endif
\ No newline at end of file
--- a/working.txt
+++ b/working.txt
@@ -1,11 +1,12 @@
-Atari Anniversary Edition Redux (USA)
-Harmful Park (Japan)
-Kyuiin (Japan)
-NASCAR 98 Collector''s Edition (USA)
-Panzer Bandit (Japan)
-Puzzle Bobble 4 (Japan)
-World Soccer Jikkyou Winning Eleven 3 - World Cup France '98 (Japan) (Taikenban)
-Worms (USA)
-San Francisco Rush - Extreme Racing (USA)
-Samurai Shodown III - Blades of Blood (USA)
-Time Crisis - Project Titan (USA)
\ No newline at end of file
+Atari Anniversary Edition Redux (USA)
+Harmful Park (Japan)
+Kyuiin (Japan)
+NASCAR 98 Collector''s Edition (USA)
+Panzer Bandit (Japan)
+Puzzle Bobble 4 (Japan)
+World Soccer Jikkyou Winning Eleven 3 - World Cup France '98 (Japan) (Taikenban)
+Worms (USA)
+San Francisco Rush - Extreme Racing (USA)
+Samurai Shodown III - Blades of Blood (USA)
+Time Crisis - Project Titan (USA)
+WipeOut XL
\ No newline at end of file
--