[
  {
    "path": ".clangd",
    "content": "# As clang/clangd's MIPS-I support is still experimental, some minor changes to\n# the GCC arguments it picks up from CMake are required in order to prevent it\n# from erroring out. Additionally, specifying the target architecture manually\n# fixes some edge cases (such as CMake emitting 8.3 format paths on Windows and\n# breaking clangd's target autodetection).\n\nCompileFlags:\n  Add:    [ --target=mipsel-none-elf, -march=mips1 ]\n  Remove: [ -march, -mno-llsc, -mdivide-breaks ]\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style             = tab\nindent_size              = 4\ncharset                  = utf-8\nend_of_line              = lf\ntrim_trailing_whitespace = true\ninsert_final_newline     = true\n\n[{*.yml,*.yaml,.clangd}]\nindent_style = space\nindent_size  = 2\n"
  },
  {
    "path": ".github/scripts/buildToolchain.sh",
    "content": "#!/bin/bash\n\nROOT_DIR=\"$(pwd)\"\nBINUTILS_VERSION=\"2.43\"\nGCC_VERSION=\"14.2.0\"\nNUM_JOBS=\"4\"\n\nif [ $# -eq 2 ]; then\n\tPACKAGE_NAME=\"$1\"\n\tTARGET_NAME=\"$2\"\n\tBUILD_OPTIONS=\"\"\nelif [ $# -eq 3 ]; then\n\tPACKAGE_NAME=\"$1\"\n\tTARGET_NAME=\"$2\"\n\tBUILD_OPTIONS=\"--build=x86_64-linux-gnu --host=$3\"\nelse\n\techo \"Usage: $0 <package name> <target triplet> [host triplet]\"\n\texit 0\nfi\n\n## Download binutils and GCC\n\nif [ ! -d binutils-$BINUTILS_VERSION ]; then\n\twget \"https://ftpmirror.gnu.org/gnu/binutils/binutils-$BINUTILS_VERSION.tar.xz\" \\\n\t\t|| exit 1\n\ttar Jxf binutils-$BINUTILS_VERSION.tar.xz \\\n\t\t|| exit 1\n\n\trm -f binutils-$BINUTILS_VERSION.tar.xz\nfi\n\nif [ ! -d gcc-$GCC_VERSION ]; then\n\twget \"https://ftpmirror.gnu.org/gnu/gcc/gcc-$GCC_VERSION/gcc-$GCC_VERSION.tar.xz\" \\\n\t\t|| exit 1\n\ttar Jxf gcc-$GCC_VERSION.tar.xz \\\n\t\t|| exit 1\n\n\tcd gcc-$GCC_VERSION\n\tcontrib/download_prerequisites \\\n\t\t|| exit 1\n\n\tcd ..\n\trm -f gcc-$GCC_VERSION.tar.xz\nfi\n\n## Build binutils\n\nmkdir -p binutils-build\ncd binutils-build\n\n../binutils-$BINUTILS_VERSION/configure \\\n\t--prefix=\"$ROOT_DIR/$PACKAGE_NAME\" \\\n\t$BUILD_OPTIONS \\\n\t--target=$TARGET_NAME \\\n\t--with-float=soft \\\n\t--disable-docs \\\n\t--disable-nls \\\n\t--disable-werror \\\n\t|| exit 2\nmake -j $NUM_JOBS \\\n\t|| exit 2\nmake install-strip \\\n\t|| exit 2\n\ncd ..\nrm -rf binutils-build\n\n## Build GCC\n\nmkdir -p gcc-build\ncd gcc-build\n\n../gcc-$GCC_VERSION/configure \\\n\t--prefix=\"$ROOT_DIR/$PACKAGE_NAME\" \\\n\t$BUILD_OPTIONS \\\n\t--target=$TARGET_NAME \\\n\t--with-float=soft \\\n\t--disable-docs \\\n\t--disable-nls \\\n\t--disable-werror \\\n\t--disable-libada \\\n\t--disable-libssp \\\n\t--disable-libquadmath \\\n\t--disable-threads \\\n\t--disable-libgomp \\\n\t--disable-libstdcxx-pch \\\n\t--disable-hosted-libstdcxx \\\n\t--enable-languages=c,c++ \\\n\t--without-isl \\\n\t--without-headers \\\n\t--with-gnu-as \\\n\t--with-gnu-ld \\\n\t|| exit 3\nmake -j $NUM_JOBS \\\n\t|| exit 3\nmake install-strip \\\n\t|| exit 3\n\ncd ..\nrm -rf gcc-build\n\n## Package toolchain\n\n#cd $PACKAGE_NAME\n\n#zip -9 -r ../$PACKAGE_NAME-$GCC_VERSION.zip . \\\n#\t|| exit 4\n\n#cd ..\n#rm -rf $PACKAGE_NAME\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "# The GCC toolchain is stored in the GitHub Actions cache after being built. To\n# minimize build times, the toolchain build step is skipped if there is a cached\n# copy of the toolchain that has not expired.\n\nname: Build examples\non:   [ push, pull_request ]\n\njobs:\n  build:\n    name:    Run build\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Initialize toolchain cache\n      id:   cache\n      uses: actions/cache@v3\n      with:\n        key:  toolchain\n        path: gcc-mipsel-none-elf\n\n    - name: Fetch repo contents\n      uses: actions/checkout@v4\n      with:\n        path: ps1-bare-metal\n\n    - name: Install prerequisites\n      run: |\n        sudo apt-get update -y\n        sudo apt-get install -y --no-install-recommends ninja-build\n\n    - name: Set up Python virtual environment\n      run: |\n        python3 -m venv ps1-bare-metal/env\n        source ps1-bare-metal/env/bin/activate\n        pip3 install -r ps1-bare-metal/tools/requirements.txt\n\n    - name: Build GCC toolchain\n      if:   ${{ steps.cache.outputs.cache-hit != 'true' }}\n      run: |\n        ps1-bare-metal/.github/scripts/buildToolchain.sh gcc-mipsel-none-elf mipsel-none-elf\n\n    - name: Build examples\n      run: |\n        cd ps1-bare-metal\n        cmake --preset debug -DTOOLCHAIN_PATH=${{ github.workspace }}/gcc-mipsel-none-elf/bin\n        cmake --build build\n\n    - name: Upload build artifacts\n      uses: actions/upload-artifact@v4\n      with:\n        name: build\n        path: ps1-bare-metal/build\n"
  },
  {
    "path": ".gitignore",
    "content": "# Do not include any hidden metadata saved by apps and the OS.\ndesktop.ini\n.DS_Store\n.vscode/\n\n# Do not include any built or cached files.\nbuild/\nenv/\n.cache/\n__pycache__/\n*.pyc\n*.pyo\n\n# Do not include user-specific workspace and configuration files.\n*.code-workspace\nCMakeUserPresets.json\n"
  },
  {
    "path": ".markdownlint.json",
    "content": "{\n\t\"line-length\": {\n\t\t\"tables\": false\n\t},\n\n\t\"emphasis-style\": false,\n\t\"no-inline-html\": false\n}\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "# ps1-bare-metal - (C) 2023-2025 spicyjpeg\n#\n# Permission to use, copy, modify, and/or distribute this software for any\n# purpose with or without fee is hereby granted, provided that the above\n# copyright notice and this permission notice appear in all copies.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n# PERFORMANCE OF THIS SOFTWARE.\n\ncmake_minimum_required(VERSION 3.25)\n\n# Set the path to the toolchain file, which will configure CMake to use the MIPS\n# toolchain rather than its default compiler and proceed in turn to execute\n# setup.cmake.\nset(CMAKE_TOOLCHAIN_FILE \"${CMAKE_CURRENT_LIST_DIR}/cmake/toolchain.cmake\")\n\n# Tell CMake about the project. The VERSION, DESCRIPTION and HOMEPAGE_URL fields\n# are optional, but the project name and LANGUAGES field should be present.\nproject(\n\tps1-bare-metal\n\tLANGUAGES    C CXX ASM\n\tVERSION      1.0.0\n\tDESCRIPTION  \"PlayStation 1 bare-metal C examples\"\n\tHOMEPAGE_URL \"https://github.com/spicyjpeg/ps1-bare-metal\"\n)\n\n# Set up compiler flags and initialize the Python environment used to run the\n# scripts in the tools directory.\ninclude(cmake/setup.cmake)\ninclude(cmake/tools.cmake)\n\n#if(\"${PSXAVENC_PATH}\" STREQUAL \"PSXAVENC_PATH-NOTFOUND\")\n\t#set(skipAudioExamples ON)\n\t#message(WARNING \"Unable to find psxavenc. All examples that require it for \\\n#audio encoding will be skipped.\")\n#endif()\n\n# Build a \"common\" library containing code shared across all examples and link\n# it by default into every executable.\nadd_library(\n\tcommon OBJECT\n\tsrc/libc/clz.s\n\tsrc/libc/crt0.c\n\tsrc/libc/cxxsupport.cpp\n\tsrc/libc/malloc.c\n\tsrc/libc/misc.c\n\tsrc/libc/setjmp.s\n\tsrc/libc/string.c\n\tsrc/libc/string.s\n\tsrc/ps1/cache.s\n\tsrc/vendor/printf.c\n)\ntarget_include_directories(\n\tcommon PUBLIC\n\tsrc\n\tsrc/libc\n)\nlink_libraries(common)\n\n# Build the examples and convert any required assets.\naddPS1Executable(example00_helloWorld    src/00_helloWorld/main.c)\naddPS1Executable(example01_basicGraphics src/01_basicGraphics/main.c)\naddPS1Executable(example02_doubleBuffer  src/02_doubleBuffer/main.c)\naddPS1Executable(example03_dmaChain      src/03_dmaChain/main.c)\n\naddPS1Executable(example04_textures src/04_textures/main.c)\nconvertImage(\n\tsrc/04_textures/texture.png 16\n\texample04/textureData.dat\n)\naddBinaryFile(example04_textures textureData \"${PROJECT_BINARY_DIR}/example04/textureData.dat\")\n\naddPS1Executable(example05_palettes src/05_palettes/main.c)\nconvertImage(\n\tsrc/05_palettes/texture.png 4\n\texample05/textureData.dat\n\texample05/paletteData.dat\n)\naddBinaryFile(example05_palettes textureData \"${PROJECT_BINARY_DIR}/example05/textureData.dat\")\naddBinaryFile(example05_palettes paletteData \"${PROJECT_BINARY_DIR}/example05/paletteData.dat\")\n\naddPS1Executable(\n\texample06_fonts\n\tsrc/06_fonts/gpu.c\n\tsrc/06_fonts/main.c\n)\nconvertImage(\n\tsrc/06_fonts/font.png 4\n\texample06/fontTexture.dat\n\texample06/fontPalette.dat\n)\naddBinaryFile(example06_fonts fontTexture \"${PROJECT_BINARY_DIR}/example06/fontTexture.dat\")\naddBinaryFile(example06_fonts fontPalette \"${PROJECT_BINARY_DIR}/example06/fontPalette.dat\")\n\naddPS1Executable(\n\texample07_orderingTable\n\tsrc/07_orderingTable/gpu.c\n\tsrc/07_orderingTable/main.c\n)\n\naddPS1Executable(\n\texample08_spinningCube\n\tsrc/08_spinningCube/gpu.c\n\tsrc/08_spinningCube/main.c\n\tsrc/08_spinningCube/trig.c\n)\n\naddPS1Executable(\n\texample09_controllers\n\tsrc/09_controllers/font.c\n\tsrc/09_controllers/gpu.c\n\tsrc/09_controllers/main.c\n)\nconvertImage(\n\tsrc/09_controllers/font.png 4\n\texample09/fontTexture.dat\n\texample09/fontPalette.dat\n)\naddBinaryFile(example09_controllers fontTexture \"${PROJECT_BINARY_DIR}/example09/fontTexture.dat\")\naddBinaryFile(example09_controllers fontPalette \"${PROJECT_BINARY_DIR}/example09/fontPalette.dat\")\n"
  },
  {
    "path": "CMakePresets.json",
    "content": "{\n\t\"version\": 6,\n\t\"cmakeMinimumRequired\": {\n\t\t\"major\": 3,\n\t\t\"minor\": 25,\n\t\t\"patch\": 0\n\t},\n\t\"configurePresets\": [\n\t\t{\n\t\t\t\"name\":        \"debug\",\n\t\t\t\"displayName\": \"Debug build\",\n\t\t\t\"description\": \"Build the project with no optimization and assertions enabled.\",\n\t\t\t\"generator\":   \"Ninja\",\n\t\t\t\"binaryDir\":   \"${sourceDir}/build\",\n\t\t\t\"cacheVariables\": {\n\t\t\t\t\"CMAKE_BUILD_TYPE\": \"Debug\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\":        \"release\",\n\t\t\t\"displayName\": \"Release build\",\n\t\t\t\"description\": \"Build the project with performance optimization and assertions stripped.\",\n\t\t\t\"generator\":   \"Ninja\",\n\t\t\t\"binaryDir\":   \"${sourceDir}/build\",\n\t\t\t\"cacheVariables\": {\n\t\t\t\t\"CMAKE_BUILD_TYPE\": \"Release\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\":        \"min-size-release\",\n\t\t\t\"displayName\": \"Minimum size release build\",\n\t\t\t\"description\": \"Build the project with size optimization and assertions stripped.\",\n\t\t\t\"generator\":   \"Ninja\",\n\t\t\t\"binaryDir\":   \"${sourceDir}/build\",\n\t\t\t\"cacheVariables\": {\n\t\t\t\t\"CMAKE_BUILD_TYPE\": \"MinSizeRel\"\n\t\t\t}\n\t\t}\n\t]\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 spicyjpeg\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "\n# PlayStation 1 bare-metal C examples\n\nThis repository contains a series of homebrew tutorials and well-commented\nexamples for the original Sony PlayStation, built using no external SDKs or\ntools other than an up-to-date, unmodified GCC toolchain targeting the MIPS\narchitecture, CMake as the build system and some Python scripts.\n\nThe following examples are currently available:\n\n| #   | Screenshot                                                                    | Description                                                                       |\n| --: | :---------------------------------------------------------------------------- | :-------------------------------------------------------------------------------- |\n|   0 |                                                                               | [Printing \"hello world\" over the serial port](src/00_helloWorld/main.c)           |\n|   1 | <img alt=\"Example 1\" src=\"src/01_basicGraphics/screenshot.png\" width=\"100\" /> | [Initializing the GPU and drawing basic graphics](src/01_basicGraphics/main.c)    |\n|   2 | <img alt=\"Example 2\" src=\"src/02_doubleBuffer/screenshot.png\" width=\"100\" />  | [Adding double buffering and animated graphics](src/02_doubleBuffer/main.c)       |\n|   3 | <img alt=\"Example 3\" src=\"src/03_dmaChain/screenshot.png\" width=\"100\" />      | [Improving GPU drawing efficiency using DMA chains](src/03_dmaChain/main.c)       |\n|   4 | <img alt=\"Example 4\" src=\"src/04_textures/screenshot.png\" width=\"100\" />      | [Uploading a texture to VRAM and using it](src/04_textures/main.c)                |\n|   5 | <img alt=\"Example 5\" src=\"src/05_palettes/screenshot.png\" width=\"100\" />      | [Using indexed color textures and color palettes](src/05_palettes/main.c)         |\n|   6 | <img alt=\"Example 6\" src=\"src/06_fonts/screenshot.png\" width=\"100\" />         | [Implementing spritesheets and simple font rendering](src/06_fonts/main.c)        |\n|   7 | <img alt=\"Example 7\" src=\"src/07_orderingTable/screenshot.png\" width=\"100\" /> | [Using ordering tables to control GPU drawing order](src/07_orderingTable/main.c) |\n|   8 | <img alt=\"Example 8\" src=\"src/08_spinningCube/screenshot.png\" width=\"100\" />  | [Drawing a 3D spinning cube using the GTE](src/08_spinningCube/main.c)            |\n|   9 | <img alt=\"Example 9\" src=\"src/09_controllers/screenshot.png\" width=\"100\" />   | [Getting input from connected controllers](src/09_controllers/main.c)             |\n\nNew examples showing how to make use of more hardware features will be added\nover time.\n\n## Building the examples\n\n### Installing dependencies\n\nThe following dependencies are required in order to compile the examples:\n\n- CMake 3.25 or later;\n- Python 3.10 or later;\n- [Ninja](https://ninja-build.org/);\n- a recent GCC toolchain configured for the `mipsel-none-elf` target triplet\n  (toolchains targeting `mipsel-linux-gnu` will generally work as well, but are\n  not recommended as the ones available in most distros' package managers tend\n  to be outdated or configured improperly).\n\nThe toolchain can be installed on Windows through\n[the `mips` script from the pcsx-redux project](https://github.com/grumpycoders/pcsx-redux/tree/main/src/mips/psyqo/GETTING_STARTED.md#windows),\non macOS using\n[Homebrew](https://github.com/grumpycoders/pcsx-redux/tree/main/src/mips/psyqo/GETTING_STARTED.md#macos)\nor on Linux by\n[spawning it from source](https://github.com/grumpycoders/pcsx-redux/blob/main/tools/linux-mips/spawn-compiler.sh),\nand should be added to your `PATH` environment variable in order to let CMake\nfind it. If you have any of the open-source PS1 SDKs installed there is a good\nchance you already have a suitable toolchain set up (try running\n`mipsel-none-elf-gcc` and `mipsel-linux-gnu-gcc` in a terminal). The other\ndependencies can be obtained through a package manager.\n\nThe Python scripts require a few additional dependencies, which can be installed\nin a virtual environment by running the following commands from the root\ndirectory of the repository:\n\n```bash\n# Windows (using PowerShell)\npy -m venv env\nenv\\Scripts\\Activate.ps1\npy -m pip install -r tools\\requirements.txt\n\n# Windows (using Cygwin/MSys2), Linux or macOS\npython3 -m venv env\nsource env/bin/activate\npip3 install -r tools/requirements.txt\n```\n\n### Building with an IDE\n\nMany IDEs and text editors feature out-of-the-box support for CMake, so you\nshould be able to import the repo into your IDE of choice and immediately get a\nworking \"build\" button once the toolchain is set up. If you are using VS Code,\ninstalling the\n[CMake Tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools)\nand [clangd](https://clangd.llvm.org) extensions for build integration as well\nas context-sensitive suggestions is highly recommended.\n\nIf the toolchain is not listed in your `PATH` environment variable, you will\nhave to set the `TOOLCHAIN_PATH` CMake variable to the full path to your\ntoolchain's `bin` subdirectory (e.g. `/opt/mipsel-linux-gnu/bin`) using your\nIDE's CMake cache editor. See your IDE's documentation for information on\naccessing the cache editor; in VS Code with the CMake Tools extension, the\neditor can be opened by selecting \"Edit CMake Cache (UI)\" from the command\npalette (Ctrl+Shift+P).\n\n### Building from the command line\n\nIf you cannot use an IDE or prefer working from the command line, simply run\nthese two commands from the repository's root:\n\n```bash\ncmake --preset debug\ncmake --build build\n```\n\nIf you are unfamiliar with CMake, the first command is what's known as the\n*configure command* and prepares the build directory for the second command,\nwhich actually runs the compiler and generates the executables. Once the build\ndirectory is prepared you'll no longer have to run the configure command unless\nyou edit the CMake scripts to e.g. add new examples or source files.\n\nYou may replace `debug` with `release` to enable release mode, which will turn\non additional compiler optimizations, remove assertions and produce smaller\nbinaries. Replacing it with `min-size-release` will further optimize the\nexecutables for size at the expense of performance.\n\nIf the toolchain is not listed in your `PATH` environment variable, you will\nhave to pass the path to its `bin` subdirectory to the configure command via the\n`-DTOOLCHAIN_PATH` option, like this:\n\n```bash\ncmake --preset debug -DTOOLCHAIN_PATH=/opt/mipsel-linux-gnu/bin\n```\n\n### Floating point support\n\nThe PlayStation does not have a floating point unit. While GCC can still provide\nsupport for floats through emulation, some prebuilt versions of the\n`mipsel-none-elf` and `mipsel-linux-gnu` toolchains ship with floating point\nemulation partially or fully disabled at build time. For this reason, the build\nscripts do not explicitly enable it.\n\nIf you have a toolchain known to have full support for software floating point\n(e.g. one you built yourself with the appropriate options) but that does not\nhave it enabled by default, you may force it by adding the following line to\n`CMakeLists.txt`:\n\n```cmake\ntarget_compile_options(flags INTERFACE -msoft-float)\n```\n\nKeep in mind that software floats are particularly slow, code-heavy and shall be\navoided at all costs. The included printf library has been modified to disable\nthe `%f` specifier, regardless of whether the toolchain supports software\nfloats, in order to reduce code size.\n\n## Running the examples\n\n### Using an emulator\n\nThe build scripts will compile each example into a `.psexe` file. This is the\nPS1 BIOS's native executable format and is also supported by pretty much every\nPS1 emulator, making it straightforward to run the examples through emulation.\nThe following emulators are recommended for development work:\n\n- [DuckStation](https://github.com/stenzek/duckstation);\n- [PCSX-Redux](https://github.com/grumpycoders/pcsx-redux) (not to be confused\n  with PCSX-R), somewhat less accurate but comes with extensive debugging tools\n  and scripting support.\n\nNote that both DuckStation and Redux default to using a dynamic recompiler\n(dynarec) in order to boost performance at the cost of accuracy. The dynarec is\nincompatible with either emulator's debugger and can be prone to timing issues;\nit's thus recommended to disable it and switch to interpreted CPU mode instead.\n\nUsing other emulators is strongly discouraged as more or less all of them are\noutdated and known to be inaccurate. In particular, **the emulators listed**\n**below are broken in many ways** and will struggle to run anything not made\nusing Sony's libraries or not following their practices.\n\n- ePSXe, pSX, XEBRA;\n- PCSX forks other than Redux (PCSX-R, Rearmed and so on);\n- Beetle PSX, SwanStation and other RetroArch-specific hacked up forks of\n  existing emulators;\n- MAME and anything based on it - there are ongoing efforts to significantly\n  improve its accuracy but they still have to be upstreamed;\n- Sony's official emulators (the PS3 and PSP's built-in backwards compatibility\n  feature, POPS/POPStarter on the PS2).\n\nThe following emulators have generally acceptable accuracy but are not\nrecommended due to their poor user experience:\n\n- Mednafen (hard to set up, not as well documented as the other options);\n- no$psx (Windows only, rarely updated, too many bugs and idiosyncracies in the\n  UI).\n\n### Using real hardware\n\nAt some point you will likely want to run your code on an actual PlayStation.\nThe two main ways to do so are:\n\n- using a loader program such as\n  [Unirom](https://github.com/JonathanDotCel/unirom8_bootdisc_and_firmware_for_ps1),\n  which will allow you to temporarily load the executable into RAM through the\n  serial port on the back of the console;\n- by authoring a disc image and either burning it to a CD-R or loading it onto\n  an optical drive emulator.\n\nCD-ROM image creation is not currently covered here as it involves using\nspecialized tools and, depending on the region and revision of your PS1, a\nlicense file. If you have a non-Japanese region unit with a modchip or softmod\n(e.g. Unirom installed to a memory card), you may simply rename your executable\nto `PSX.EXE`, burn it to a CD-R and the console *should* run it. Unirom also\ncomes with a file browser that will let you launch any executable on the disc.\n\nNote that a PS2 is *not* a PS1, not even in its \"native\" (non-POPS) backwards\ncompatibility mode. It's a chimera of real hardware, emulated hardware,\nsemi-emulated hardware that\n[differs across revisions](https://israpps.github.io/PPC-Monitor/docs/Architecture%20Overview.html)\nand game-specific hacks in\n[multiple](https://psi-rockin.github.io/ps2tek/#biospscompatibility)\n[places](https://israpps.github.io/PPC-Monitor/docs/XPARAM.html). As such, it's\nbest left alongside the inaccurate emulators and not used for PS1 homebrew\ndevelopment.\n\n## Modifying the code\n\nIf you want to write your own examples or projects, here's a quick overview of\nthe non-example subfolders in the `src` directory:\n\n- `src/libc` contains a minimal implementation of the C standard library, which\n  should be enough for most purposes. Some functions have been replaced with\n  optimized assembly implementations.\n- `src/ps1` contains a basic support library for the hardware, consisting mostly\n  of definitions for hardware registers and GPU commands.\n- `src/vendor` is for third-party libraries (currently only the printf library).\n\nIf you create a new folder and want its contents to be built, remember to add it\nto `CMakeLists.txt` (you may use the existing entries as a reference) and rerun\nboth the CMake configure and build commands afterwards.\n\n## Background\n\nI have been occasionally asked if I could provide an example of PS1 homebrew\nprogramming that is completely self-contained, permissively licensed and does\nnot depend on an external SDK. While there are a number of PS1 SDK options\naround (including some I have contributed to), their workflows may not suit\neveryone and some of the most popular options are additionally encumbered with\nlegal issues that make them a non-starter for commercial homebrew games, and/or\nlimitations that are hard to work around. As I have been moving away from using\nsuch libraries myself, I set out to take what I am currently building for my\nprojects, clean it up and turn it into a tutorial series for other people to\nfollow.\n\nI want this repo to be an introduction to bare-metal platforms and the PS1 for\nanybody who already has some experience with C/C++ but not necessarily with the\nprocess of linking and compiling, the internals of a standard library, the way\nthreads and IRQs work at the kernel level and so on. I strongly believe that\ndemystifying the inner workings of a platform can go a long way when it comes to\nhelping people understand it. Most 8-bit and 16-bit consoles have received a lot\nof attention and excellent bare-metal tutorials have been written for them, so I\ndon't get why people shall just give up and use ancient proprietary SDKs from\nthe 1990s when it comes to the PS1.\n\n## License\n\nEverything in this repository, including the vendored copy of\n[Marco Paland's printf library](https://github.com/mpaland/printf), is licensed\nunder the MIT license (or the functionally equivalent ISC license). The only\n\"hard\" requirements are attribution and preserving the license notice; you may\notherwise freely use any of the code for both non-commercial and commercial\npurposes (such as a paid homebrew game or a book or course).\n\n## See also\n\n- If you are just getting started with PS1 development, Rodrigo Copetti's\n  [PlayStation Architecture](https://copetti.org/writings/consoles/playstation)\n  is a great overview of the console's hardware and capabilities.\n- The [PlayStation specifications (psx-spx)](https://psx-spx.consoledev.net/)\n  page, adapted and expanded from no$psx's documentation, is the main hardware\n  reference for bare-metal PS1 programming and emulation.\n- [573in1](https://github.com/spicyjpeg/573in1) is a real world example of a\n  moderately complex project built on top of the scripts and support library\n  provided in this repository.\n- If you need help or wish to discuss PS1 homebrew development more in general,\n  you may want to check out the\n  [PSX.Dev Discord server](https://discord.gg/QByKPpH).\n"
  },
  {
    "path": "cmake/executable.ld",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\nENTRY(_start)\n\nMEMORY {\n\t/*\n\t * Only 2 MB of main RAM are available on a regular console, but some\n\t * development kits are fitted with 8 MB and most emulators have an option\n\t * to extend RAM to 8 MB as well. You may change the length below from\n\t * 0x1f0000 to 0x7f0000 allow the linker to use the additional memory. Note\n\t * that the first 64 KB at 0x80000000-0x8000ffff are always reserved for use\n\t * by the kernel.\n\t */\n\tAPP_RAM (rwx) : ORIGIN = 0x80010000, LENGTH = 0x1f0000\n}\n\nSECTIONS {\n\t/* Code sections */\n\n\t.text : ALIGN(4) {\n\t\t_textStart = .;\n\n\t\t*(.text .text.* .gnu.linkonce.t.*)\n\t\t*(.plt .MIPS.stubs)\n\t} > APP_RAM\n\n\t.rodata : {\n\t\t*(.rodata .rodata.* .gnu.linkonce.r.*)\n\n\t\t_textEnd = .;\n\t} > APP_RAM\n\n\t/* Global constructor/destructor arrays */\n\n\t.preinit_array : ALIGN(4) {\n\t\t_preinitArrayStart = .;\n\n\t\tKEEP(*(.preinit_array))\n\n\t\t_preinitArrayEnd = .;\n\t} > APP_RAM\n\n\t.init_array : ALIGN(4) {\n\t\t_initArrayStart = .;\n\n\t\tKEEP(*(SORT(.init_array.*) SORT(.ctors.*)))\n\t\tKEEP(*(.init_array .ctors))\n\n\t\t_initArrayEnd = .;\n\t} > APP_RAM\n\n\t.fini_array : ALIGN(4) {\n\t\t_finiArrayStart = .;\n\n\t\tKEEP(*(.fini_array .dtors))\n\t\tKEEP(*(SORT(.fini_array.*) SORT(.dtors.*)))\n\n\t\t_finiArrayEnd = .;\n\t} > APP_RAM\n\n\t/* Data sections */\n\n\t.data : {\n\t\t_dataStart = .;\n\n\t\t*(.data .data.* .gnu.linkonce.d.*)\n\t} > APP_RAM\n\n\t/*\n\t * Set _gp (copied to $gp) to point to the beginning of .sdata plus 0x8000,\n\t * so anything within .sdata and .sbss can be accessed using the $gp\n\t * register as base plus a signed 16-bit immediate.\n\t */\n\t.sdata : {\n\t\t_gp = ALIGN(16) + 0x7ff0;\n\n\t\t*(.sdata .sdata.* .gnu.linkonce.s.*)\n\n\t\t_dataEnd = .;\n\t} > APP_RAM\n\n\t/*\n\t * Ensure the entire BSS region is aligned at both ends in order to allow\n\t * for fast clearing.\n\t */\n\t.sbss (NOLOAD) : ALIGN(4) {\n\t\t_bssStart = .;\n\n\t\t*(.sbss .sbss.* .gnu.linkonce.sb.*)\n\t\t*(.scommon)\n\t} > APP_RAM\n\n\t.bss (NOLOAD) : {\n\t\t*(.bss .bss.* .gnu.linkonce.b.*)\n\t\t*(COMMON)\n\n\t\t.       = ALIGN(4);\n\t\t_bssEnd = .;\n\t} > APP_RAM\n\n\t/* Dummy sections */\n\n\t.dummy (NOLOAD) : {\n\t\tKEEP(*(.dummy))\n\t} > APP_RAM\n\n\t/DISCARD/ : {\n\t\t*(.note.* .gnu_debuglink .gnu.lto_*)\n\t\t*(.MIPS.abiflags)\n\t}\n}\n"
  },
  {
    "path": "cmake/setup.cmake",
    "content": "# ps1-bare-metal - (C) 2023-2024 spicyjpeg\n#\n# Permission to use, copy, modify, and/or distribute this software for any\n# purpose with or without fee is hereby granted, provided that the above\n# copyright notice and this permission notice appear in all copies.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n# PERFORMANCE OF THIS SOFTWARE.\n\ncmake_minimum_required(VERSION 3.25)\n\n# Override the default file extensions for executables and libraries. This is\n# not strictly required, but it makes CMake's behavior more consistent.\nset(CMAKE_EXECUTABLE_SUFFIX     .elf)\nset(CMAKE_STATIC_LIBRARY_PREFIX lib)\nset(CMAKE_STATIC_LIBRARY_SUFFIX .a)\n\n# Add libgcc.a (-lgcc) to the set of libraries linked to all executables by\n# default. This library ships with GCC and must be linked to anything compiled\n# with it.\nlink_libraries(-lgcc)\n\n# Create a dummy \"flags\" library that is not made up of any files, but adds the\n# appropriate compiler and linker flags for PS1 executables to anything linked\n# to it. The library is then added to the default set of libraries.\nadd_library   (flags INTERFACE)\nlink_libraries(flags)\n\ntarget_compile_features(\n\tflags INTERFACE\n\tc_std_17\n\tcxx_std_20\n)\ntarget_compile_options(\n\tflags INTERFACE\n\t\t-g\n\t\t-Wall\n\t\t-Wa,--strip-local-absolute\n\t\t-ffreestanding\n\t\t-fno-builtin\n\t\t-fno-pic\n\t\t-nostdlib\n\t\t-fdata-sections\n\t\t-ffunction-sections\n\t\t-fsigned-char\n\t\t-fno-strict-overflow\n\t\t-march=r3000\n\t\t-mabi=32\n\t\t-mfp32\n\t\t#-msoft-float\n\t\t-mno-mt\n\t\t-mno-llsc\n\t\t-mno-abicalls\n\t\t-mgpopt\n\t\t-mno-extern-sdata\n\t\t-G8\n\t$<$<COMPILE_LANGUAGE:CXX>:\n\t\t# These options will only be added when compiling C++ source files.\n\t\t-fno-exceptions\n\t\t-fno-rtti\n\t\t-fno-unwind-tables\n\t\t-fno-threadsafe-statics\n\t\t-fno-use-cxa-atexit\n\t>\n\t$<IF:$<CONFIG:Debug>,\n\t\t# These options will only be added if CMAKE_BUILD_TYPE is set to Debug.\n\t\t-Og\n\t\t-mdivide-breaks\n\t,\n\t\t# These options will be added if CMAKE_BUILD_TYPE is not set to Debug.\n\t\t#-O3\n\t\t#-flto\n\t\t-mno-check-zero-division\n\t>\n)\ntarget_link_options(\n\tflags INTERFACE\n\t\t-static\n\t\t-nostdlib\n\t\t-Wl,-gc-sections\n\t\t-G8\n\t\t\"-T${CMAKE_CURRENT_LIST_DIR}/executable.ld\"\n)\n\n# Define a helper function to embed binary data into executables and libraries.\nfunction(addBinaryFile target name path)\n\tset(asmFile \"${PROJECT_BINARY_DIR}/includes/${target}_${name}.s\")\n\tcmake_path(ABSOLUTE_PATH path OUTPUT_VARIABLE fullPath)\n\n\t# Generate an assembly listing that uses the .incbin directive to embed the\n\t# file and add it to the executable's list of source files. This may look\n\t# hacky, but it works and lets us easily customize the symbol name (i.e. the\n\t# name of the \"array\" that will contain the file's data).\n\tfile(\n\t\tCONFIGURE\n\t\tOUTPUT  \"${asmFile}\"\n\t\tCONTENT [[\n.section .rodata.${name}, \"a\"\n.balign 8\n\n.global ${name}\n.type ${name}, @object\n.size ${name}, (${name}_end - ${name})\n\n${name}:\n\t.incbin \"${fullPath}\"\n${name}_end:\n]]\n\t\tESCAPE_QUOTES\n\t\tNEWLINE_STYLE LF\n\t)\n\n\ttarget_sources(${target} PRIVATE \"${asmFile}\")\n\tset_source_files_properties(\n\t\t\"${asmFile}\" PROPERTIES OBJECT_DEPENDS \"${fullPath}\"\n\t)\nendfunction()\n\nfunction(addBinaryFileWithSize target name sizeName path)\n\tset(asmFile \"${PROJECT_BINARY_DIR}/includes/${target}_${name}.s\")\n\tcmake_path(ABSOLUTE_PATH path OUTPUT_VARIABLE fullPath)\n\n\tfile(\n\t\tCONFIGURE\n\t\tOUTPUT  \"${asmFile}\"\n\t\tCONTENT [[\n.section .rodata.${name}, \"a\"\n.balign 8\n\n.global ${name}\n.type ${name}, @object\n.size ${name}, (${name}_end - ${name})\n\n${name}:\n\t.incbin \"${fullPath}\"\n${name}_end:\n\n.section .rodata.${sizeName}, \"a\"\n.balign 4\n\n.global ${sizeName}\n.type ${sizeName}, @object\n.size ${sizeName}, 4\n\n${sizeName}:\n\t.int (${name}_end - ${name})\n]]\n\t\tESCAPE_QUOTES\n\t\tNEWLINE_STYLE LF\n\t)\n\n\ttarget_sources(${target} PRIVATE \"${asmFile}\")\n\tset_source_files_properties(\n\t\t\"${asmFile}\" PROPERTIES OBJECT_DEPENDS \"${fullPath}\"\n\t)\nendfunction()\n"
  },
  {
    "path": "cmake/toolchain.cmake",
    "content": "# ps1-bare-metal - (C) 2023-2024 spicyjpeg\n#\n# Permission to use, copy, modify, and/or distribute this software for any\n# purpose with or without fee is hereby granted, provided that the above\n# copyright notice and this permission notice appear in all copies.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n# PERFORMANCE OF THIS SOFTWARE.\n\ncmake_minimum_required(VERSION 3.25)\n\n# Create a user-editable variable to allow for a custom toolchain path to be\n# specified by passing -DTOOLCHAIN_PATH=... to CMake.\nset(\n\tTOOLCHAIN_PATH \"\"\n\tCACHE PATH \"Directory containing GCC toolchain (if not listed in PATH)\"\n)\n\n# Prevent CMake from using any host compiler by manually overriding the platform\n# and setting it to \"generic\" (i.e. no defaults).\nset(CMAKE_SYSTEM_NAME      Generic)\nset(CMAKE_SYSTEM_PROCESSOR mipsel)\n\nset(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)\nset(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)\nset(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)\n\n# Tell CMake not to run the linker when testing the toolchain and to pass our\n# custom variables through to autogenerated \"compiler test\" projects. This will\n# prevent the compiler detection process from erroring out.\nset(CMAKE_TRY_COMPILE_TARGET_TYPE        STATIC_LIBRARY)\nset(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES TOOLCHAIN_PATH)\n\n# Always generate compile_commands.json when building. This allows some IDEs and\n# tools (such as clangd) to automatically configure include directories and\n# other options.\nset(CMAKE_EXPORT_COMPILE_COMMANDS ON)\n\n## Toolchain path setup\n\n# Attempt to locate the GCC command in the provided path (if any) as well as in\n# the system's standard paths for programs such as the ones listed in the PATH\n# environment variable. Try to use a mipsel-none-elf toolchain over a\n# mipsel-linux-gnu one if available.\nfind_program(\n\tgccBinaryPath\n\t\tmipsel-none-elf-gcc\n\t\tmipsel-unknown-elf-gcc\n\t\tmipsel-linux-gnu-gcc\n\tHINTS\n\t\t\"${TOOLCHAIN_PATH}\"\n\t\t\"${TOOLCHAIN_PATH}/bin\"\n\t\t\"${TOOLCHAIN_PATH}/../bin\"\n\tNO_CACHE\n)\nif(\"${gccBinaryPath}\" STREQUAL \"gccBinaryPath-NOTFOUND\")\n\tmessage(FATAL_ERROR \"Unable to find the GCC toolchain. Ensure your PATH \\\nenvironment variable includes the full path to the toolchain's /bin subfolder, \\\nor pass -DTOOLCHAIN_PATH=... to CMake to specify its location manually.\")\nendif()\n\ncmake_path(GET gccBinaryPath PARENT_PATH toolchainPath)\n\n# If a valid path was not provided but GCC was found, overwrite the variable to\n# avoid searching again the next time the project is configured.\nif(NOT IS_DIRECTORY \"${TOOLCHAIN_PATH}\")\n\tset(\n\t\tTOOLCHAIN_PATH \"${toolchainPath}\"\n\t\tCACHE PATH \"Directory containing GCC toolchain (if not listed in PATH)\"\n\t\tFORCE\n\t)\nendif()\n\n# Set the paths to all tools required by CMake. The appropriate extension for\n# executables (.exe on Windows, none on Unix) is extracted from the path to GCC\n# using a regular expression, as CMake does not otherwise expose it when\n# cross-compiling.\nstring(REGEX MATCH \"^(.+-)gcc(.*)$\" dummy \"${gccBinaryPath}\")\n\nset(CMAKE_ASM_COMPILER \"${CMAKE_MATCH_1}gcc${CMAKE_MATCH_2}\")\nset(CMAKE_C_COMPILER   \"${CMAKE_MATCH_1}gcc${CMAKE_MATCH_2}\")\nset(CMAKE_CXX_COMPILER \"${CMAKE_MATCH_1}g++${CMAKE_MATCH_2}\")\nset(CMAKE_AR           \"${CMAKE_MATCH_1}ar${CMAKE_MATCH_2}\")\nset(CMAKE_LINKER       \"${CMAKE_MATCH_1}gcc${CMAKE_MATCH_2}\")\nset(CMAKE_RANLIB       \"${CMAKE_MATCH_1}ranlib${CMAKE_MATCH_2}\")\nset(CMAKE_OBJCOPY      \"${CMAKE_MATCH_1}objcopy${CMAKE_MATCH_2}\")\nset(CMAKE_OBJDUMP      \"${CMAKE_MATCH_1}objdump${CMAKE_MATCH_2}\")\nset(CMAKE_NM           \"${CMAKE_MATCH_1}nm${CMAKE_MATCH_2}\")\nset(CMAKE_SIZE         \"${CMAKE_MATCH_1}size${CMAKE_MATCH_2}\")\nset(CMAKE_STRIP        \"${CMAKE_MATCH_1}strip${CMAKE_MATCH_2}\")\nset(CMAKE_READELF      \"${CMAKE_MATCH_1}readelf${CMAKE_MATCH_2}\")\n"
  },
  {
    "path": "cmake/tools.cmake",
    "content": "# ps1-bare-metal - (C) 2023-2025 spicyjpeg\n#\n# Permission to use, copy, modify, and/or distribute this software for any\n# purpose with or without fee is hereby granted, provided that the above\n# copyright notice and this permission notice appear in all copies.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n# PERFORMANCE OF THIS SOFTWARE.\n\ncmake_minimum_required(VERSION 3.25)\n\n# Create a user-editable variable to allow for the path to the Python virtual\n# environment to be customized by passing -DVENV_PATH=... to CMake.\nset(\n\tVENV_PATH \"${PROJECT_SOURCE_DIR}/env\"\n\tCACHE PATH \"Directory containing Python virtual environment\"\n)\n\n# If no virtual environment was activated prior to building, attempt to set up\n# the one specified by the variable.\nif(NOT IS_DIRECTORY \"$ENV{VIRTUAL_ENV}\")\n\tif(IS_DIRECTORY \"${VENV_PATH}\")\n\t\tset(ENV{VIRTUAL_ENV} \"${VENV_PATH}\")\n\telse()\n\t\tmessage(FATAL_ERROR \"Unable to find the Python virtual environment. \\\nRefer to the README to set one up in ${VENV_PATH}, or pass -DVENV_PATH=... to \\\nCMake to specify its location manually.\")\n\tendif()\nendif()\n\n# Activate the environment by letting CMake search for its Python interpreter.\nset(Python3_FIND_VIRTUALENV ONLY)\nfind_package(Python3 3.10 REQUIRED COMPONENTS Interpreter)\n\n# Define some helper functions that rely on the Python scripts in the tools\n# folder.\nfunction(addPS1Executable name)\n\tadd_executable(${name} ${ARGN})\n\n\t# As the GCC linker outputs executables in ELF format, a script must be run\n\t# on each compiled binary to convert it to the .psexe format expected by the\n\t# PS1. By default all custom commands run from the build directory, so paths\n\t# to files in the source tree must be relative to ${PROJECT_SOURCE_DIR} or\n\t# ${CMAKE_CURRENT_FUNCTION_LIST_DIR}.\n\tadd_custom_command(\n\t\tTARGET     ${name} POST_BUILD\n\t\tBYPRODUCTS ${name}.psexe\n\t\tCOMMAND\n\t\t\t\"${Python3_EXECUTABLE}\"\n\t\t\t\"${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../tools/convertExecutable.py\"\n\t\t\t\"$<TARGET_FILE:${name}>\"\n\t\t\t${name}.psexe\n\t\tVERBATIM\n\t)\nendfunction()\n\nfunction(addPS1ExecutableAdv name loadAddress stackTop region)\n\tadd_executable     (${name} ${ARGN})\n\ttarget_link_options(${name} PRIVATE \"-Ttext=${loadAddress}\")\n\n\tadd_custom_command(\n\t\tTARGET     ${name} POST_BUILD\n\t\tBYPRODUCTS \"${name}.psexe\"\n\t\tCOMMAND\n\t\t\t\"${Python3_EXECUTABLE}\"\n\t\t\t\"${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../tools/convertExecutable.py\"\n\t\t\t-r \"${region}\"\n\t\t\t-s \"${stackTop}\"\n\t\t\t\"$<TARGET_FILE:${name}>\"\n\t\t\t${name}.psexe\n\t\tVERBATIM\n\t)\nendfunction()\n\nfunction(convertImage input bpp)\n\tadd_custom_command(\n\t\tOUTPUT  ${ARGN}\n\t\tDEPENDS \"${PROJECT_SOURCE_DIR}/${input}\"\n\t\tCOMMAND\n\t\t\t\"${Python3_EXECUTABLE}\"\n\t\t\t\"${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../tools/convertImage.py\"\n\t\t\t-b ${bpp}\n\t\t\t\"${PROJECT_SOURCE_DIR}/${input}\"\n\t\t\t${ARGN}\n\t\tVERBATIM\n\t)\nendfunction()\n\n# Let CMake locate psxavenc automatically (or rely on the user overriding it by\n# passing -DPSXAVENC_PATH=...) and define a helper function to encode audio\n# samples if available.\nfind_program(\n\tPSXAVENC_PATH psxavenc\n\tDOC \"Path to psxavenc executable (if not present in PATH)\"\n)\n\nfunction(convertAudioSample input sampleRate output)\n\tif(\"${PSXAVENC_PATH}\" STREQUAL \"PSXAVENC_PATH-NOTFOUND\")\n\t\tmessage(FATAL_ERROR \"Unable to find psxavenc. Ensure your PATH \\\nenvironment variable includes the full path to the directory containing it, or \\\npass -DPSXAVENC_PATH=... to CMake to specify its location manually.\")\n\tendif()\n\n\tadd_custom_command(\n\t\tOUTPUT  \"${output}\"\n\t\tDEPENDS \"${PROJECT_SOURCE_DIR}/${input}\"\n\t\tCOMMAND\n\t\t\t\"${PSXAVENC_PATH}\"\n\t\t\t-t spu\n\t\t\t-f ${sampleRate}\n\t\t\t\"${PROJECT_SOURCE_DIR}/${input}\"\n\t\t\t\"${output}\"\n\t\tVERBATIM\n\t)\nendfunction()\n"
  },
  {
    "path": "src/00_helloWorld/main.c",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n/*\n * Let's start with the absolute basics, the obligatory hello world program. We\n * are going to just print \"Hello world!\" in an infinite loop; since we don't\n * have the luxury of a terminal or even anything resembling a \"text mode\" on\n * the GPU, we'll use the PS1's serial port instead.\n *\n * The serial port can be found on the back of the console on all models except\n * the PSone, and can be connected to a PC with an appropriately modified link\n * cable. Internally it is connected to the secondary serial interface, known as\n * SIO1 (as opposed to SIO0 which is wired to the controller and memory card\n * ports). SIO1 is controlled through I/O registers, which we're going to\n * manipulate to get it to output our message.\n */\n\n#include \"ps1/registers.h\"\n\nstatic void printCharacter(char ch) {\n\t// Wait until the serial interface is ready to send a new byte, then write\n\t// it to the data register.\n\t// NOTE: the serial interface checks for an external signal (CTS) and will\n\t// *not* send any data until it is asserted. To avoid blocking forever if\n\t// CTS is not asserted, we have to check for it manually and abort if\n\t// necessary.\n\twhile (\n\t\t(SIO_STAT(1) & (SIO_STAT_TX_NOT_FULL | SIO_STAT_CTS)) == SIO_STAT_CTS\n\t)\n\t\t__asm__ volatile(\"\");\n\n\tif (SIO_STAT(1) & SIO_STAT_CTS)\n\t\tSIO_DATA(1) = ch;\n}\n\nint main(int argc, const char **argv) {\n\t// Reset the serial interface and initialize it to output data at 115200bps,\n\t// 8 data bits, 1 stop bit and no parity.\n\tSIO_CTRL(1) = SIO_CTRL_RESET;\n\n\tSIO_MODE(1) = 0\n\t\t| SIO_MODE_BAUD_DIV1\n\t\t| SIO_MODE_DATA_8\n\t\t| SIO_MODE_STOP_1;\n\tSIO_BAUD(1) = F_CPU / 115200;\n\tSIO_CTRL(1) = 0\n\t\t| SIO_CTRL_TX_ENABLE\n\t\t| SIO_CTRL_RX_ENABLE\n\t\t| SIO_CTRL_RTS;\n\n\t// Output \"Hello world!\" in a loop, one character at a time.\n\tfor (;;) {\n\t\tconst char *str = \"Hello world!\\n\";\n\n\t\tfor (; *str; str++)\n\t\t\tprintCharacter(*str);\n\t}\n\n\t// We're not actually going to return. Unless a loader was used to launch\n\t// the program, returning from main() would crash the console as there would\n\t// be nothing to return to.\n\treturn 0;\n}\n"
  },
  {
    "path": "src/01_basicGraphics/main.c",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n/*\n * In this tutorial we're going to initialize the GPU, set it up and display\n * some simple hardware-rendered graphics (namely, a shaded triangle).\n *\n * While the PS1's GPU may appear complicated and daunting, its principle of\n * operation is in actual fact very simple. At a high level it is simply a\n * \"rasterization machine\" capable of drawing triangles, quads, rectangles and\n * lines in 2D space (with 3D transformations being entirely the CPU's\n * responsibility) and of displaying a rectangular cutout of its 1024x512x16bpp\n * framebuffer, which resides in dedicated memory (VRAM). It is controlled using\n * only two registers, one (GP0) for drawing commands and the other (GP1) for\n * display control and other commands; we're going to see how to configure the\n * video output and draw our triangle by writing the right commands to these\n * registers.\n *\n * This tutorial will use the ps1/gpucmd.h header file I wrote, which contains\n * enumerations and inline functions for all commands supported by the GPU. If\n * you wish to write such a header yourself, you'll want to check out the\n * GPU register and command documentation at:\n *     https://psx-spx.consoledev.net/graphicsprocessingunitgpu\n */\n\n#include <stdio.h>\n#include \"ps1/gpucmd.h\"\n#include \"ps1/registers.h\"\n\nstatic void setupGPU(GP1VideoMode mode, int width, int height) {\n\t// Set the origin of the displayed framebuffer. These \"magic\" values,\n\t// derived from the GPU's internal clocks, will center the picture on most\n\t// displays and upscalers.\n\tint x = 0x760;\n\tint y = (mode == GP1_MODE_PAL) ? 0xa3 : 0x88;\n\n\t// Set the resolution. The GPU provides a number of fixed horizontal (256,\n\t// 320, 368, 512, 640) and vertical (240-256, 480-512) resolutions to pick\n\t// from, which affect how fast pixels are output and thus how \"stretched\"\n\t// the framebuffer will appear.\n\tGP1HorizontalRes horizontalRes = GP1_HRES_320;\n\tGP1VerticalRes   verticalRes   = GP1_VRES_256;\n\n\t// Set the number of displayed rows and columns. These values are in GPU\n\t// clock units rather than pixels, thus they are dependent on the selected\n\t// resolution.\n\tint offsetX = (width  * gp1_clockMultiplierH(horizontalRes)) / 2;\n\tint offsetY = (height / gp1_clockDividerV(verticalRes))      / 2;\n\n\t// Hand all parameters over to the GPU by sending GP1 commands. The last\n\t// command unblanks (turns on) the video output, as resetting the GPU blanks\n\t// it by default.\n\tGPU_GP1 = gp1_resetGPU();\n\tGPU_GP1 = gp1_fbRangeH(x - offsetX, x + offsetX);\n\tGPU_GP1 = gp1_fbRangeV(y - offsetY, y + offsetY);\n\tGPU_GP1 = gp1_fbMode(\n\t\thorizontalRes,\n\t\tverticalRes,\n\t\tmode,\n\t\tfalse,\n\t\tGP1_COLOR_16BPP\n\t);\n\tGPU_GP1 = gp1_dispBlank(false);\n}\n\nstatic void waitForGP0Ready(void) {\n\t// Block until the GPU reports to be ready to accept commands through its\n\t// status register (which has the same address as GP1 but is read-only).\n\twhile (!(GPU_GP1 & GP1_STAT_CMD_READY))\n\t\t__asm__ volatile(\"\");\n}\n\n#define SCREEN_WIDTH  320\n#define SCREEN_HEIGHT 240\n\nint main(int argc, const char **argv) {\n\t// Initialize the serial interface. The initSerialIO() function is defined\n\t// in src/libc/misc.c and does basically the same things we did in the\n\t// previous example. Afterwards, we'll be able to use puts(), printf() and\n\t// a few other standard I/O functions as they are declared in the libc\n\t// directory (with printf() being provided by a third-party library).\n\tinitSerialIO(115200);\n\n\t// Read the GPU's status register to check if it was left in PAL or NTSC\n\t// mode by the BIOS/loader.\n\tif ((GPU_GP1 & GP1_STAT_FB_MODE_BITMASK) == GP1_STAT_FB_MODE_PAL) {\n\t\tputs(\"Using PAL mode\");\n\t\tsetupGPU(GP1_MODE_PAL, SCREEN_WIDTH, SCREEN_HEIGHT);\n\t} else {\n\t\tputs(\"Using NTSC mode\");\n\t\tsetupGPU(GP1_MODE_NTSC, SCREEN_WIDTH, SCREEN_HEIGHT);\n\t}\n\n\t// Wait for the GPU to become ready, then send some GP0 commands to tell it\n\t// which area of the framebuffer we want to draw to and enable dithering.\n\twaitForGP0Ready();\n\tGPU_GP0 = gp0_setPage(0, true, false);\n\tGPU_GP0 = gp0_fbOffset1(0, 0);\n\tGPU_GP0 = gp0_fbOffset2(SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1);\n\tGPU_GP0 = gp0_fbOrigin(0, 0);\n\n\t// Send a VRAM fill command to quickly fill our area with solid gray. Note\n\t// that the coordinates passed to this specific command are *not* relative\n\t// to the ones we've just sent to the GPU!\n\twaitForGP0Ready();\n\tGPU_GP0 = gp0_rgb(64, 64, 64) | gp0_vramFill();\n\tGPU_GP0 = gp0_xy(0, 0);\n\tGPU_GP0 = gp0_xy(SCREEN_WIDTH, SCREEN_HEIGHT);\n\n\t// Tell the GPU to draw a Gouraud shaded triangle whose vertices are red,\n\t// green and blue respectively at the center of our drawing area.\n\twaitForGP0Ready();\n\tGPU_GP0 = gp0_rgb(255, 0, 0) | gp0_shadedTriangle(true, false, false);\n\tGPU_GP0 = gp0_xy(SCREEN_WIDTH / 2, 32);\n\tGPU_GP0 = gp0_rgb(0, 255, 0);\n\tGPU_GP0 = gp0_xy(32, SCREEN_HEIGHT - 32);\n\tGPU_GP0 = gp0_rgb(0, 0, 255);\n\tGPU_GP0 = gp0_xy(SCREEN_WIDTH - 32, SCREEN_HEIGHT - 32);\n\n\t// Send a GP1 command to set the origin of the area we want to display.\n\tGPU_GP1 = gp1_fbOffset(0, 0);\n\n\t// Continue by doing nothing.\n\tfor (;;)\n\t\t__asm__ volatile(\"\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/02_doubleBuffer/main.c",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n/*\n * We saw how to initialize the GPU and get basic graphics on screen in the last\n * tutorial. It's now time to add motion to the mix: we're going to draw a\n * square which, in true DVD player screensaver fashion, will bounce around on\n * the screen.\n *\n * This may sound simple in theory, but there are a few caveats we'll have to\n * look out for. First of all we will need some sort of timer for our animation,\n * ideally something synchronized to the display output in order to avoid\n * updating the position of our square while the picture is still being sent by\n * the GPU to the monitor (and stabilize the frame rate). We'll also have to\n * ensure the frame we are sending in the first place is not actively being\n * updated by the GPU, otherwise screen tearing will be prominent. Hiding a\n * frame while it is being drawn may sound tricky, but there is a very simple\n * way to accomplish it: we are going to keep *two* frames in VRAM, and draw one\n * while the other is being displayed. Once drawing is done and the other frame\n * has been fully sent to the display, we're going to swap the buffers (so that\n * the newly rendered frame will be displayed) and start over.\n *\n * This is an extremely common practice (the device you are looking at right now\n * is no doubt using it) known as double buffering, and you can read more about\n * it here:\n *     https://gameprogrammingpatterns.com/double-buffer.html\n */\n\n#include <stdbool.h>\n#include <stdio.h>\n#include \"ps1/gpucmd.h\"\n#include \"ps1/registers.h\"\n\nstatic void setupGPU(GP1VideoMode mode, int width, int height) {\n\tint x = 0x760;\n\tint y = (mode == GP1_MODE_PAL) ? 0xa3 : 0x88;\n\n\tGP1HorizontalRes horizontalRes = GP1_HRES_320;\n\tGP1VerticalRes   verticalRes   = GP1_VRES_256;\n\n\tint offsetX = (width  * gp1_clockMultiplierH(horizontalRes)) / 2;\n\tint offsetY = (height / gp1_clockDividerV(verticalRes))      / 2;\n\n\tGPU_GP1 = gp1_resetGPU();\n\tGPU_GP1 = gp1_fbRangeH(x - offsetX, x + offsetX);\n\tGPU_GP1 = gp1_fbRangeV(y - offsetY, y + offsetY);\n\tGPU_GP1 = gp1_fbMode(\n\t\thorizontalRes,\n\t\tverticalRes,\n\t\tmode,\n\t\tfalse,\n\t\tGP1_COLOR_16BPP\n\t);\n\tGPU_GP1 = gp1_dispBlank(false);\n}\n\nstatic void waitForGP0Ready(void) {\n\twhile (!(GPU_GP1 & GP1_STAT_CMD_READY))\n\t\t__asm__ volatile(\"\");\n}\n\nstatic void waitForVSync(void) {\n\t// The GPU won't tell us directly whenever it is done sending a frame to the\n\t// display, but it will send a signal to another peripheral known as the\n\t// interrupt controller (which will be covered in a future tutorial). We can\n\t// thus wait until the interrupt controller's vertical blank flag gets set,\n\t// then reset (acknowledge) it so that it can be set again by the GPU.\n\twhile (!(IRQ_STAT & (1 << IRQ_VSYNC)))\n\t\t__asm__ volatile(\"\");\n\n\tIRQ_STAT = ~(1 << IRQ_VSYNC);\n}\n\n#define SCREEN_WIDTH  320\n#define SCREEN_HEIGHT 240\n\nint main(int argc, const char **argv) {\n\tinitSerialIO(115200);\n\n\tif ((GPU_GP1 & GP1_STAT_FB_MODE_BITMASK) == GP1_STAT_FB_MODE_PAL) {\n\t\tputs(\"Using PAL mode\");\n\t\tsetupGPU(GP1_MODE_PAL, SCREEN_WIDTH, SCREEN_HEIGHT);\n\t} else {\n\t\tputs(\"Using NTSC mode\");\n\t\tsetupGPU(GP1_MODE_NTSC, SCREEN_WIDTH, SCREEN_HEIGHT);\n\t}\n\n\tint x = 0, velocityX = 1;\n\tint y = 0, velocityY = 1;\n\n\tbool usingSecondFrame = false;\n\n\tfor (;;) {\n\t\t// Determine the VRAM location of the current frame. We're going to\n\t\t// place the two frames next to each other in VRAM, at (0, 0) and\n\t\t// (320, 0) respectively.\n\t\tint frameX = usingSecondFrame ? SCREEN_WIDTH : 0;\n\t\tint frameY = 0;\n\n\t\tusingSecondFrame = !usingSecondFrame;\n\n\t\t// Tell the GPU which area of VRAM belongs to the frame we're going to\n\t\t// use and enable dithering.\n\t\twaitForGP0Ready();\n\t\tGPU_GP0 = gp0_setPage(0, true, false);\n\t\tGPU_GP0 = gp0_fbOffset1(frameX, frameY);\n\t\tGPU_GP0 = gp0_fbOffset2(\n\t\t\tframeX + SCREEN_WIDTH  - 1,\n\t\t\tframeY + SCREEN_HEIGHT - 1\n\t\t);\n\t\tGPU_GP0 = gp0_fbOrigin(frameX, frameY);\n\n\t\t// Fill the framebuffer with solid gray.\n\t\twaitForGP0Ready();\n\t\tGPU_GP0 = gp0_rgb(64, 64, 64) | gp0_vramFill();\n\t\tGPU_GP0 = gp0_xy(frameX, frameY);\n\t\tGPU_GP0 = gp0_xy(SCREEN_WIDTH, SCREEN_HEIGHT);\n\n\t\t// Draw the yellow bouncing square using a rectangle command.\n\t\twaitForGP0Ready();\n\t\tGPU_GP0 = gp0_rgb(255, 255, 0) | gp0_rectangle(false, false, false);\n\t\tGPU_GP0 = gp0_xy(x, y);\n\t\tGPU_GP0 = gp0_xy(32, 32);\n\n\t\t// Update the position of the bouncing square.\n\t\tx += velocityX;\n\t\ty += velocityY;\n\n\t\tif ((x <= 0) || (x >= (SCREEN_WIDTH - 32)))\n\t\t\tvelocityX = -velocityX;\n\t\tif ((y <= 0) || (y >= (SCREEN_HEIGHT - 32)))\n\t\t\tvelocityY = -velocityY;\n\n\t\t// Wait for the GPU to finish drawing and displaying the contents of the\n\t\t// previous frame, then tell it to start sending the newly drawn frame\n\t\t// to the video output.\n\t\twaitForGP0Ready();\n\t\twaitForVSync();\n\n\t\tGPU_GP1 = gp1_fbOffset(frameX, frameY);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/03_dmaChain/main.c",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n/*\n * In the previous two examples we saw how to control the GPU and draw graphics\n * by writing commands directly to the GP0 and GP1 registers. While this\n * approach is simple and easy to understand, it also limits performance: the\n * CPU always has to wait for the GPU to go idle before being able to send it a\n * new command, otherwise the GPU may miss it; similarly, the GPU can only\n * process commands while the CPU is actively feeding it.\n *\n * To get around these limitations the GPU (along with most of the PS1's other\n * peripherals) can instead be given access to RAM and read GP0 commands from it\n * automatically, without needing the CPU to feed it. This is accomplished\n * through a middleman peripheral known as the direct memory access (DMA)\n * controller, and is the key to high-performance graphics on the PS1. We'll see\n * how to allocate a buffer in RAM, fill it with commands and then set up the\n * GPU's DMA channel to read from it in the background while we're preparing the\n * next frame's command buffer.\n */\n\n#include <assert.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdio.h>\n#include \"ps1/gpucmd.h\"\n#include \"ps1/registers.h\"\n\nstatic void setupGPU(GP1VideoMode mode, int width, int height) {\n\tint x = 0x760;\n\tint y = (mode == GP1_MODE_PAL) ? 0xa3 : 0x88;\n\n\tGP1HorizontalRes horizontalRes = GP1_HRES_320;\n\tGP1VerticalRes   verticalRes   = GP1_VRES_256;\n\n\tint offsetX = (width  * gp1_clockMultiplierH(horizontalRes)) / 2;\n\tint offsetY = (height / gp1_clockDividerV(verticalRes))      / 2;\n\n\tGPU_GP1 = gp1_resetGPU();\n\tGPU_GP1 = gp1_fbRangeH(x - offsetX, x + offsetX);\n\tGPU_GP1 = gp1_fbRangeV(y - offsetY, y + offsetY);\n\tGPU_GP1 = gp1_fbMode(\n\t\thorizontalRes,\n\t\tverticalRes,\n\t\tmode,\n\t\tfalse,\n\t\tGP1_COLOR_16BPP\n\t);\n\tGPU_GP1 = gp1_dispBlank(false);\n\n\t// Enable and reset the GPU's DMA channel, then tell the GPU to fetch GP0\n\t// commands from DMA whenever available.\n\tDMA_DPCR         |= DMA_DPCR_CH_ENABLE(DMA_GPU);\n\tDMA_CHCR(DMA_GPU) = 0;\n\n\tGPU_GP1 = gp1_dmaRequestMode(GP1_DREQ_GP0_WRITE);\n}\n\nstatic void waitForGP0Ready(void) {\n\twhile (!(GPU_GP1 & GP1_STAT_CMD_READY))\n\t\t__asm__ volatile(\"\");\n}\n\nstatic void waitForVSync(void) {\n\twhile (!(IRQ_STAT & (1 << IRQ_VSYNC)))\n\t\t__asm__ volatile(\"\");\n\n\tIRQ_STAT = ~(1 << IRQ_VSYNC);\n}\n\nstatic void sendGPULinkedList(const void *data) {\n\t// Wait until the GPU's DMA unit has finished sending data and is ready.\n\twhile (DMA_CHCR(DMA_GPU) & DMA_CHCR_ENABLE)\n\t\t__asm__ volatile(\"\");\n\n\t// Make sure the pointer is aligned to 32 bits (4 bytes). The DMA engine is\n\t// not capable of reading unaligned data.\n\tassert(!((uint32_t) data % 4));\n\n\t// Give DMA a pointer to the beginning of the data and tell it to send it in\n\t// linked list mode. The DMA unit will start parsing a chain of \"packets\"\n\t// from RAM, with each packet being made up of a 32-bit header followed by\n\t// zero or more 32-bit commands to be sent to the GP0 register.\n\tDMA_MADR(DMA_GPU) = (uint32_t) data;\n\tDMA_CHCR(DMA_GPU) = 0\n\t\t| DMA_CHCR_WRITE\n\t\t| DMA_CHCR_MODE_LIST\n\t\t| DMA_CHCR_ENABLE;\n}\n\n// Define a structure we'll allocate our linked list packets into. We are going\n// to use a fixed-size buffer and keep a pointer to the beginning of its free\n// area, incrementing it whenever we allocate a new packet.\n#define DMA_MAX_CHUNK_SIZE      16\n#define GPU_CHAIN_BUFFER_SIZE 1024\n\ntypedef struct {\n\tuint32_t data[GPU_CHAIN_BUFFER_SIZE];\n\tuint32_t *nextPacket;\n} GPUDMAChain;\n\nstatic uint32_t *allocateGP0Packet(GPUDMAChain *chain, int numCommands) {\n\t// Ensure no more than 16 command words are sent to the GPU at once, as\n\t// sending larger packets may overrun the GP0 command FIFO and result in\n\t// corrupted data.\n\tassert((numCommands >= 0) && (numCommands <= DMA_MAX_CHUNK_SIZE));\n\n\t// Grab the current pointer to the next packet then increment it to allocate\n\t// a new packet. We have to allocate an extra word for the packet's header,\n\t// which will contain the number of GP0 commands the packet is made up of as\n\t// well as a pointer to the next packet (or a special \"terminator\" value to\n\t// tell the DMA unit to stop).\n\tuint32_t *ptr      = chain->nextPacket;\n\tchain->nextPacket += numCommands + 1;\n\n\t// Write the header and set its pointer to point to the next packet that\n\t// will be allocated in the buffer.\n\t*ptr = gp0_tag(numCommands, chain->nextPacket);\n\n\t// Make sure we haven't yet run out of space for future packets or a linked\n\t// list terminator, then return a pointer to the packet's first GP0 command.\n\tassert(chain->nextPacket < &(chain->data)[GPU_CHAIN_BUFFER_SIZE]);\n\n\treturn &ptr[1];\n}\n\n#define SCREEN_WIDTH  320\n#define SCREEN_HEIGHT 240\n\nint main(int argc, const char **argv) {\n\tinitSerialIO(115200);\n\n\tif ((GPU_GP1 & GP1_STAT_FB_MODE_BITMASK) == GP1_STAT_FB_MODE_PAL) {\n\t\tputs(\"Using PAL mode\");\n\t\tsetupGPU(GP1_MODE_PAL, SCREEN_WIDTH, SCREEN_HEIGHT);\n\t} else {\n\t\tputs(\"Using NTSC mode\");\n\t\tsetupGPU(GP1_MODE_NTSC, SCREEN_WIDTH, SCREEN_HEIGHT);\n\t}\n\n\tint x = 0, velocityX = 1;\n\tint y = 0, velocityY = 1;\n\n\t// Allocate a double command buffer in order to let the GPU keep fetching\n\t// commands while the CPU is filling up the other buffer. See the previous\n\t// example for more details on double buffering.\n\tGPUDMAChain dmaChains[2];\n\tbool     usingSecondFrame = false;\n\n\tfor (;;) {\n\t\tint bufferX = usingSecondFrame ? SCREEN_WIDTH : 0;\n\t\tint bufferY = 0;\n\n\t\tGPUDMAChain *chain = &dmaChains[usingSecondFrame];\n\t\tusingSecondFrame   = !usingSecondFrame;\n\n\t\tuint32_t *ptr;\n\n\t\t// Display the frame that was just drawn by the GPU (if any). We are\n\t\t// going to overwrite its respective DMA chain afterwards, as the GPU no\n\t\t// longer needs it.\n\t\tGPU_GP1 = gp1_fbOffset(bufferX, bufferY);\n\n\t\t// Reset the pointer to the next packet to the beginning of the buffer.\n\t\tchain->nextPacket = chain->data;\n\n\t\t// Create a new DMA packet for each GP0 command we're sending. Splitting\n\t\t// up each command like this will make sure the DMA channel won't try to\n\t\t// send them too quickly and end up overflowing the GPU's internal\n\t\t// command processor.\n\t\tptr    = allocateGP0Packet(chain, 4);\n\t\tptr[0] = gp0_setPage(0, true, false);\n\t\tptr[1] = gp0_fbOffset1(bufferX, bufferY);\n\t\tptr[2] = gp0_fbOffset2(\n\t\t\tbufferX + SCREEN_WIDTH  - 1,\n\t\t\tbufferY + SCREEN_HEIGHT - 1\n\t\t);\n\t\tptr[3] = gp0_fbOrigin(bufferX, bufferY);\n\n\t\tptr    = allocateGP0Packet(chain, 3);\n\t\tptr[0] = gp0_rgb(64, 64, 64) | gp0_vramFill();\n\t\tptr[1] = gp0_xy(bufferX, bufferY);\n\t\tptr[2] = gp0_xy(SCREEN_WIDTH, SCREEN_HEIGHT);\n\n\t\tptr    = allocateGP0Packet(chain, 3);\n\t\tptr[0] = gp0_rgb(255, 255, 0) | gp0_rectangle(false, false, false);\n\t\tptr[1] = gp0_xy(x, y);\n\t\tptr[2] = gp0_xy(32, 32);\n\n\t\t// Terminate the linked list and tell the DMA engine to stop reading by\n\t\t// appending a terminator header to it.\n\t\t*(chain->nextPacket) = gp0_endTag(0);\n\n\t\tx += velocityX;\n\t\ty += velocityY;\n\n\t\tif ((x <= 0) || (x >= (SCREEN_WIDTH - 32)))\n\t\t\tvelocityX = -velocityX;\n\t\tif ((y <= 0) || (y >= (SCREEN_HEIGHT - 32)))\n\t\t\tvelocityY = -velocityY;\n\n\t\t// Wait for the previous frame to be displayed, then start sending the\n\t\t// newly built DMA chain in the background while the next iteration of\n\t\t// the main loop is going to run.\n\t\twaitForGP0Ready();\n\t\twaitForVSync();\n\t\tsendGPULinkedList(chain->data);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/04_textures/main.c",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n/*\n * The last example showed how to take advantage of the PS1's DMA engine to send\n * commands to the GPU efficiently in the background. While that is by far the\n * most common use case for DMA, the GPU's DMA channel has another crucial role:\n * it allows for fast data transfers from and to VRAM, which are useful for\n * uploading image data to be used as a texture when drawing.\n *\n * This example shows how to upload raw 16bpp RGB image data embedded into the\n * executable to an arbitrary location within the 1024x512 VRAM buffer, store\n * its coordinates into memory and recall them later in order to draw a textured\n * sprite on screen. The process unfortunately involves dealing with a number of\n * GPU idiosyncracies, such as its lack of support for textures larger than\n * 256x256 or its requirement for all textures to be arranged into a grid of\n * 64x256 regions of VRAM known as \"texture pages\" (a texture may span up to\n * four pages horizontally but only one vertically, so it can't e.g. cross the\n * Y=256 boundary). With those details out of the way, however, using textures\n * boils down to simply performing a DMA transfer and setting the appropriate\n * fields in GP0 commands.\n */\n\n#include <assert.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdio.h>\n#include \"ps1/gpucmd.h\"\n#include \"ps1/registers.h\"\n\nstatic void setupGPU(GP1VideoMode mode, int width, int height) {\n\tint x = 0x760;\n\tint y = (mode == GP1_MODE_PAL) ? 0xa3 : 0x88;\n\n\tGP1HorizontalRes horizontalRes = GP1_HRES_320;\n\tGP1VerticalRes   verticalRes   = GP1_VRES_256;\n\n\tint offsetX = (width  * gp1_clockMultiplierH(horizontalRes)) / 2;\n\tint offsetY = (height / gp1_clockDividerV(verticalRes))      / 2;\n\n\tGPU_GP1 = gp1_resetGPU();\n\tGPU_GP1 = gp1_fbRangeH(x - offsetX, x + offsetX);\n\tGPU_GP1 = gp1_fbRangeV(y - offsetY, y + offsetY);\n\tGPU_GP1 = gp1_fbMode(\n\t\thorizontalRes,\n\t\tverticalRes,\n\t\tmode,\n\t\tfalse,\n\t\tGP1_COLOR_16BPP\n\t);\n\tGPU_GP1 = gp1_dispBlank(false);\n\n\tDMA_DPCR         |= DMA_DPCR_CH_ENABLE(DMA_GPU);\n\tDMA_CHCR(DMA_GPU) = 0;\n\n\tGPU_GP1 = gp1_dmaRequestMode(GP1_DREQ_GP0_WRITE);\n}\n\nstatic void waitForGP0Ready(void) {\n\twhile (!(GPU_GP1 & GP1_STAT_CMD_READY))\n\t\t__asm__ volatile(\"\");\n}\n\nstatic void waitForGPUDMADone(void) {\n\twhile (DMA_CHCR(DMA_GPU) & DMA_CHCR_ENABLE)\n\t\t__asm__ volatile(\"\");\n}\n\nstatic void waitForVSync(void) {\n\twhile (!(IRQ_STAT & (1 << IRQ_VSYNC)))\n\t\t__asm__ volatile(\"\");\n\n\tIRQ_STAT = ~(1 << IRQ_VSYNC);\n}\n\nstatic void sendGPULinkedList(const void *data) {\n\twaitForGPUDMADone();\n\tassert(!((uint32_t) data % 4));\n\n\tDMA_MADR(DMA_GPU) = (uint32_t) data;\n\tDMA_CHCR(DMA_GPU) = 0\n\t\t| DMA_CHCR_WRITE\n\t\t| DMA_CHCR_MODE_LIST\n\t\t| DMA_CHCR_ENABLE;\n}\n\n#define DMA_MAX_CHUNK_SIZE 16\n\nstatic void sendVRAMData(\n\tconst void *data,\n\tint        x,\n\tint        y,\n\tint        width,\n\tint        height\n) {\n\twaitForGPUDMADone();\n\tassert(!((uint32_t) data % 4));\n\n\t// Calculate how many 32-bit words will be sent from the width and height of\n\t// the texture. If more than 16 words have to be sent, configure DMA to\n\t// split the transfer into 16-word chunks in order to make sure the GPU will\n\t// not miss any data.\n\tsize_t length = (width * height + 1) / 2;\n\tsize_t chunkSize, numChunks;\n\n\tif (length < DMA_MAX_CHUNK_SIZE) {\n\t\tchunkSize = length;\n\t\tnumChunks = 1;\n\t} else {\n\t\tchunkSize = DMA_MAX_CHUNK_SIZE;\n\t\tnumChunks = length / DMA_MAX_CHUNK_SIZE;\n\n\t\t// Make sure the length is an exact multiple of 16 words, as otherwise\n\t\t// the last chunk would be dropped (the DMA unit does not support\n\t\t// \"incomplete\" chunks). Note that this will impose limitations on the\n\t\t// size of VRAM uploads.\n\t\tassert(!(length % DMA_MAX_CHUNK_SIZE));\n\t}\n\n\t// Put the GPU into VRAM upload mode by sending the appropriate GP0 command\n\t// and our coordinates.\n\twaitForGP0Ready();\n\tGPU_GP0 = gp0_vramWrite();\n\tGPU_GP0 = gp0_xy(x, y);\n\tGPU_GP0 = gp0_xy(width, height);\n\n\t// Give DMA a pointer to the beginning of the data and tell it to send it in\n\t// slice (chunked) mode.\n\tDMA_MADR(DMA_GPU) = (uint32_t) data;\n\tDMA_BCR (DMA_GPU) = chunkSize | (numChunks << 16);\n\tDMA_CHCR(DMA_GPU) = 0\n\t\t| DMA_CHCR_WRITE\n\t\t| DMA_CHCR_MODE_SLICE\n\t\t| DMA_CHCR_ENABLE;\n}\n\n#define GPU_CHAIN_BUFFER_SIZE 1024\n\ntypedef struct {\n\tuint32_t data[GPU_CHAIN_BUFFER_SIZE];\n\tuint32_t *nextPacket;\n} GPUDMAChain;\n\nstatic uint32_t *allocateGP0Packet(GPUDMAChain *chain, int numCommands) {\n\tassert((numCommands >= 0) && (numCommands <= DMA_MAX_CHUNK_SIZE));\n\n\tuint32_t *ptr      = chain->nextPacket;\n\tchain->nextPacket += numCommands + 1;\n\n\t*ptr = gp0_tag(numCommands, chain->nextPacket);\n\tassert(chain->nextPacket < &(chain->data)[GPU_CHAIN_BUFFER_SIZE]);\n\n\treturn &ptr[1];\n}\n\n// Once our texture has been uploaded to VRAM, we are going to save the metadata\n// required to use it for drawing into this structure.\ntypedef struct {\n\tuint8_t  u, v;\n\tuint16_t width, height;\n\tuint16_t page;\n} TextureInfo;\n\nstatic void uploadTexture(\n\tTextureInfo *info,\n\tconst void  *data,\n\tint         x,\n\tint         y,\n\tint         width,\n\tint         height\n) {\n\t// Make sure the texture's size is valid. The GPU does not support textures\n\t// larger than 256x256 pixels.\n\tassert((width <= 256) && (height <= 256));\n\n\t// Upload the texture to VRAM, wait for the process to complete and flush\n\t// any previously used texture from the GPU's internal cache.\n\tsendVRAMData(data, x, y, width, height);\n\twaitForGPUDMADone();\n\tGPU_GP0 = gp0_flushCache();\n\n\t// Update the \"texture page\" attribute, a 16-bit field telling the GPU\n\t// several details about the texture such as which 64x256 page it can be\n\t// found in, its color depth and how semitransparent pixels shall be\n\t// blended.\n\tinfo->page = gp0_page(\n\t\tx /  64,\n\t\ty / 256,\n\t\tGP0_BLEND_SEMITRANS,\n\t\tGP0_COLOR_16BPP\n\t);\n\n\t// Calculate the texture's UV coordinates, i.e. its X/Y coordinates relative\n\t// to the top left corner of the texture page.\n\tinfo->u      = (uint8_t)  (x %  64);\n\tinfo->v      = (uint8_t)  (y % 256);\n\tinfo->width  = (uint16_t) width;\n\tinfo->height = (uint16_t) height;\n}\n\n#define SCREEN_WIDTH   320\n#define SCREEN_HEIGHT  240\n#define TEXTURE_WIDTH   32\n#define TEXTURE_HEIGHT  32\n\n// We're going to convert our texture into raw binary data using a Python script\n// and embed it into this extern array through CMake. See CMakeLists.txt for\n// more details.\nextern const uint8_t textureData[];\n\nint main(int argc, const char **argv) {\n\tinitSerialIO(115200);\n\n\tif ((GPU_GP1 & GP1_STAT_FB_MODE_BITMASK) == GP1_STAT_FB_MODE_PAL) {\n\t\tputs(\"Using PAL mode\");\n\t\tsetupGPU(GP1_MODE_PAL, SCREEN_WIDTH, SCREEN_HEIGHT);\n\t} else {\n\t\tputs(\"Using NTSC mode\");\n\t\tsetupGPU(GP1_MODE_NTSC, SCREEN_WIDTH, SCREEN_HEIGHT);\n\t}\n\n\t// Load the texture, placing it next to the two framebuffers in VRAM.\n\tTextureInfo texture;\n\n\tuploadTexture(\n\t\t&texture,\n\t\ttextureData,\n\t\tSCREEN_WIDTH * 2,\n\t\t0,\n\t\tTEXTURE_WIDTH,\n\t\tTEXTURE_HEIGHT\n\t);\n\n\tint x = 0, velocityX = 1;\n\tint y = 0, velocityY = 1;\n\n\tGPUDMAChain dmaChains[2];\n\tbool        usingSecondFrame = false;\n\n\tfor (;;) {\n\t\tint bufferX = usingSecondFrame ? SCREEN_WIDTH : 0;\n\t\tint bufferY = 0;\n\n\t\tGPUDMAChain *chain = &dmaChains[usingSecondFrame];\n\t\tusingSecondFrame   = !usingSecondFrame;\n\n\t\tuint32_t *ptr;\n\n\t\tGPU_GP1 = gp1_fbOffset(bufferX, bufferY);\n\n\t\tchain->nextPacket = chain->data;\n\n\t\tptr    = allocateGP0Packet(chain, 4);\n\t\tptr[0] = gp0_setPage(0, true, false);\n\t\tptr[1] = gp0_fbOffset1(bufferX, bufferY);\n\t\tptr[2] = gp0_fbOffset2(\n\t\t\tbufferX + SCREEN_WIDTH -  1,\n\t\t\tbufferY + SCREEN_HEIGHT - 1\n\t\t);\n\t\tptr[3] = gp0_fbOrigin(bufferX, bufferY);\n\n\t\tptr    = allocateGP0Packet(chain, 3);\n\t\tptr[0] = gp0_rgb(64, 64, 64) | gp0_vramFill();\n\t\tptr[1] = gp0_xy(bufferX, bufferY);\n\t\tptr[2] = gp0_xy(SCREEN_WIDTH, SCREEN_HEIGHT);\n\n\t\t// Use the texture we uploaded to draw a sprite (textured rectangle).\n\t\t// Two separate commands have to be sent: a texture page command to\n\t\t// apply our page attribute and disable dithering, followed by the\n\t\t// actual rectangle drawing command. Any subsequent rectangle commands\n\t\t// will reuse the last page set, so it's not strictly necessary to send\n\t\t// a page command for each rectangle drawn.\n\t\t// NOTE: while not covered here, triangle and quad commands have an\n\t\t// inline page attribute and do not require a separate page setting\n\t\t// command (if not to toggle dithering, which the inline page field does\n\t\t// not affect).\n\t\tptr    = allocateGP0Packet(chain, 5);\n\t\tptr[0] = gp0_setPage(texture.page, false, false);\n\t\tptr[1] = gp0_rectangle(true, true, false);\n\t\tptr[2] = gp0_xy(x, y);\n\t\tptr[3] = gp0_uv(texture.u, texture.v, 0);\n\t\tptr[4] = gp0_xy(texture.width, texture.height);\n\n\t\t*(chain->nextPacket) = gp0_endTag(0);\n\n\t\tx += velocityX;\n\t\ty += velocityY;\n\n\t\tif ((x <= 0) || (x >= (SCREEN_WIDTH - texture.width)))\n\t\t\tvelocityX = -velocityX;\n\t\tif ((y <= 0) || (y >= (SCREEN_HEIGHT - texture.height)))\n\t\t\tvelocityY = -velocityY;\n\n\t\twaitForGP0Ready();\n\t\twaitForVSync();\n\t\tsendGPULinkedList(chain->data);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/05_palettes/main.c",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n/*\n * This is a version of the previous example modified to use an indexed color\n * texture instead of a raw one. The idea behind indexed color images is\n * remarkably simple: by limiting the maximum number of unique colors in an\n * image and storing their values separately, it is possible to reduce the size\n * of the image data by replacing each pixel with an index to its color into the\n * so-called CLUT (color lookup table) or palette.\n *\n * The PS1's GPU supports two indexed color formats: 4 bits per pixel (up to 16\n * colors) and 8 bits per pixel (up to 256 colors). 4bpp and 8bpp textures are\n * stored in VRAM \"squished\" horizontally, taking up half or a quarter of the\n * size of an equivalent 16bpp texture respectively. Palettes are simply 16x1 or\n * 256x1 16bpp images that can be placed anywhere in VRAM, with some minimal\n * restrictions on alignment (their X coordinate must be a multiple of 16). This\n * example shows how to upload a palette to VRAM alongside the image and set the\n * appropriate GP0 attributes in order to let the GPU find and use it.\n */\n\n#include <assert.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdio.h>\n#include \"ps1/gpucmd.h\"\n#include \"ps1/registers.h\"\n\nstatic void setupGPU(GP1VideoMode mode, int width, int height) {\n\tint x = 0x760;\n\tint y = (mode == GP1_MODE_PAL) ? 0xa3 : 0x88;\n\n\tGP1HorizontalRes horizontalRes = GP1_HRES_320;\n\tGP1VerticalRes   verticalRes   = GP1_VRES_256;\n\n\tint offsetX = (width  * gp1_clockMultiplierH(horizontalRes)) / 2;\n\tint offsetY = (height / gp1_clockDividerV(verticalRes))      / 2;\n\n\tGPU_GP1 = gp1_resetGPU();\n\tGPU_GP1 = gp1_fbRangeH(x - offsetX, x + offsetX);\n\tGPU_GP1 = gp1_fbRangeV(y - offsetY, y + offsetY);\n\tGPU_GP1 = gp1_fbMode(\n\t\thorizontalRes,\n\t\tverticalRes,\n\t\tmode,\n\t\tfalse,\n\t\tGP1_COLOR_16BPP\n\t);\n\tGPU_GP1 = gp1_dispBlank(false);\n\n\tDMA_DPCR         |= DMA_DPCR_CH_ENABLE(DMA_GPU);\n\tDMA_CHCR(DMA_GPU) = 0;\n\n\tGPU_GP1 = gp1_dmaRequestMode(GP1_DREQ_GP0_WRITE);\n}\n\nstatic void waitForGP0Ready(void) {\n\twhile (!(GPU_GP1 & GP1_STAT_CMD_READY))\n\t\t__asm__ volatile(\"\");\n}\n\nstatic void waitForGPUDMADone(void) {\n\twhile (DMA_CHCR(DMA_GPU) & DMA_CHCR_ENABLE)\n\t\t__asm__ volatile(\"\");\n}\n\nstatic void waitForVSync(void) {\n\twhile (!(IRQ_STAT & (1 << IRQ_VSYNC)))\n\t\t__asm__ volatile(\"\");\n\n\tIRQ_STAT = ~(1 << IRQ_VSYNC);\n}\n\nstatic void sendGPULinkedList(const void *data) {\n\twaitForGPUDMADone();\n\tassert(!((uint32_t) data % 4));\n\n\tDMA_MADR(DMA_GPU) = (uint32_t) data;\n\tDMA_CHCR(DMA_GPU) = 0\n\t\t| DMA_CHCR_WRITE\n\t\t| DMA_CHCR_MODE_LIST\n\t\t| DMA_CHCR_ENABLE;\n}\n\n#define DMA_MAX_CHUNK_SIZE 16\n\nstatic void sendVRAMData(\n\tconst void *data,\n\tint        x,\n\tint        y,\n\tint        width,\n\tint        height\n) {\n\twaitForGPUDMADone();\n\tassert(!((uint32_t) data % 4));\n\n\tsize_t length = (width * height + 1) / 2;\n\tsize_t chunkSize, numChunks;\n\n\tif (length < DMA_MAX_CHUNK_SIZE) {\n\t\tchunkSize = length;\n\t\tnumChunks = 1;\n\t} else {\n\t\tchunkSize = DMA_MAX_CHUNK_SIZE;\n\t\tnumChunks = length / DMA_MAX_CHUNK_SIZE;\n\n\t\tassert(!(length % DMA_MAX_CHUNK_SIZE));\n\t}\n\n\twaitForGP0Ready();\n\tGPU_GP0 = gp0_vramWrite();\n\tGPU_GP0 = gp0_xy(x, y);\n\tGPU_GP0 = gp0_xy(width, height);\n\n\tDMA_MADR(DMA_GPU) = (uint32_t) data;\n\tDMA_BCR (DMA_GPU) = chunkSize | (numChunks << 16);\n\tDMA_CHCR(DMA_GPU) = 0\n\t\t| DMA_CHCR_WRITE\n\t\t| DMA_CHCR_MODE_SLICE\n\t\t| DMA_CHCR_ENABLE;\n}\n\n#define GPU_CHAIN_BUFFER_SIZE 1024\n\ntypedef struct {\n\tuint32_t data[GPU_CHAIN_BUFFER_SIZE];\n\tuint32_t *nextPacket;\n} GPUDMAChain;\n\nstatic uint32_t *allocateGP0Packet(GPUDMAChain *chain, int numCommands) {\n\tassert((numCommands >= 0) && (numCommands <= DMA_MAX_CHUNK_SIZE));\n\n\tuint32_t *ptr      = chain->nextPacket;\n\tchain->nextPacket += numCommands + 1;\n\n\t*ptr = gp0_tag(numCommands, chain->nextPacket);\n\tassert(chain->nextPacket < &(chain->data)[GPU_CHAIN_BUFFER_SIZE]);\n\n\treturn &ptr[1];\n}\n\n// We need to add a new entry to this structure to store the CLUT attribute,\n// another 16-bit field which will contain the coordinates of our texture's\n// palette within VRAM.\ntypedef struct {\n\tuint8_t  u, v;\n\tuint16_t width, height;\n\tuint16_t page, clut;\n} TextureInfo;\n\nstatic void uploadIndexedTexture(\n\tTextureInfo   *info,\n\tconst void    *image,\n\tconst void    *palette,\n\tint           imageX,\n\tint           imageY,\n\tint           paletteX,\n\tint           paletteY,\n\tint           width,\n\tint           height,\n\tGP0ColorDepth colorDepth\n) {\n\tassert((width <= 256) && (height <= 256));\n\n\t// Determine how large the palette is and by which factor the image is\n\t// squished horizontally in VRAM from the color depth.\n\tint numColors    = (colorDepth == GP0_COLOR_8BPP) ? 256 : 16;\n\tint widthDivider = (colorDepth == GP0_COLOR_8BPP) ?   2 :  4;\n\n\t// Make sure the palette is aligned correctly within VRAM and does not\n\t// exceed its bounds.\n\tassert(!(paletteX % 16) && ((paletteX + numColors) <= 1024));\n\n\t// Upload the image and palette data separately, then flush any previously\n\t// used texture from the GPU's internal cache.\n\tsendVRAMData(image, imageX, imageY, width / widthDivider, height);\n\twaitForGPUDMADone();\n\tsendVRAMData(palette, paletteX, paletteY, numColors, 1);\n\twaitForGPUDMADone();\n\tGPU_GP0 = gp0_flushCache();\n\n\t// Update the texture page and CLUT attributes to match the VRAM locations\n\t// of the image and palette respectively.\n\tinfo->page = gp0_page(\n\t\timageX /  64,\n\t\timageY / 256,\n\t\tGP0_BLEND_SEMITRANS,\n\t\tcolorDepth\n\t);\n\tinfo->clut = gp0_clut(paletteX / 16, paletteY);\n\n\t// UV coordinate calculation is slightly more complex than before. The GPU\n\t// expects coordinates to be in texture pixels rather than VRAM pixels, so\n\t// the U coordinate has to be multiplied by the previously computed divider.\n\tinfo->u      = (uint8_t)  ((imageX %  64) * widthDivider);\n\tinfo->v      = (uint8_t)   (imageY % 256);\n\tinfo->width  = (uint16_t) width;\n\tinfo->height = (uint16_t) height;\n}\n\n#define SCREEN_WIDTH        320\n#define SCREEN_HEIGHT       240\n#define TEXTURE_WIDTH        32\n#define TEXTURE_HEIGHT       32\n#define TEXTURE_COLOR_DEPTH GP0_COLOR_4BPP\n\n// The Python script will generate two separate files containing the image and\n// palette data respectively, so we're going to embed both into the executable.\nextern const uint8_t textureData[], paletteData[];\n\nint main(int argc, const char **argv) {\n\tinitSerialIO(115200);\n\n\tif ((GPU_GP1 & GP1_STAT_FB_MODE_BITMASK) == GP1_STAT_FB_MODE_PAL) {\n\t\tputs(\"Using PAL mode\");\n\t\tsetupGPU(GP1_MODE_PAL, SCREEN_WIDTH, SCREEN_HEIGHT);\n\t} else {\n\t\tputs(\"Using NTSC mode\");\n\t\tsetupGPU(GP1_MODE_NTSC, SCREEN_WIDTH, SCREEN_HEIGHT);\n\t}\n\n\t// Load the texture, placing the image next to the two framebuffers in VRAM\n\t// and the palette below the image.\n\tTextureInfo texture;\n\n\tuploadIndexedTexture(\n\t\t&texture,\n\t\ttextureData,\n\t\tpaletteData,\n\t\tSCREEN_WIDTH * 2,\n\t\t0,\n\t\tSCREEN_WIDTH * 2,\n\t\tTEXTURE_HEIGHT,\n\t\tTEXTURE_WIDTH,\n\t\tTEXTURE_HEIGHT,\n\t\tTEXTURE_COLOR_DEPTH\n\t);\n\n\tint x = 0, velocityX = 1;\n\tint y = 0, velocityY = 1;\n\n\tGPUDMAChain dmaChains[2];\n\tbool        usingSecondFrame = false;\n\n\tfor (;;) {\n\t\tint bufferX = usingSecondFrame ? SCREEN_WIDTH : 0;\n\t\tint bufferY = 0;\n\n\t\tGPUDMAChain *chain = &dmaChains[usingSecondFrame];\n\t\tusingSecondFrame   = !usingSecondFrame;\n\n\t\tuint32_t *ptr;\n\n\t\tGPU_GP1 = gp1_fbOffset(bufferX, bufferY);\n\n\t\tchain->nextPacket = chain->data;\n\n\t\tptr    = allocateGP0Packet(chain, 4);\n\t\tptr[0] = gp0_setPage(0, true, false);\n\t\tptr[1] = gp0_fbOffset1(bufferX, bufferY);\n\t\tptr[2] = gp0_fbOffset2(\n\t\t\tbufferX + SCREEN_WIDTH  - 1,\n\t\t\tbufferY + SCREEN_HEIGHT - 1\n\t\t);\n\t\tptr[3] = gp0_fbOrigin(bufferX, bufferY);\n\n\t\tptr    = allocateGP0Packet(chain, 3);\n\t\tptr[0] = gp0_rgb(64, 64, 64) | gp0_vramFill();\n\t\tptr[1] = gp0_xy(bufferX, bufferY);\n\t\tptr[2] = gp0_xy(SCREEN_WIDTH, SCREEN_HEIGHT);\n\n\t\t// Draw the sprite, almost identically to how we did it in the previous\n\t\t// example. Notice how the CLUT attribute is being passed to the GPU.\n\t\tptr    = allocateGP0Packet(chain, 5);\n\t\tptr[0] = gp0_setPage(texture.page, false, false);\n\t\tptr[1] = gp0_rectangle(true, true, false);\n\t\tptr[2] = gp0_xy(x, y);\n\t\tptr[3] = gp0_uv(texture.u, texture.v, texture.clut);\n\t\tptr[4] = gp0_xy(texture.width, texture.height);\n\n\t\t*(chain->nextPacket) = gp0_endTag(0);\n\n\t\tx += velocityX;\n\t\ty += velocityY;\n\n\t\tif ((x <= 0) || (x >= (SCREEN_WIDTH - texture.width)))\n\t\t\tvelocityX = -velocityX;\n\t\tif ((y <= 0) || (y >= (SCREEN_HEIGHT - texture.height)))\n\t\t\tvelocityY = -velocityY;\n\n\t\twaitForGP0Ready();\n\t\twaitForVSync();\n\t\tsendGPULinkedList(chain->data);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/06_fonts/gpu.c",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#include <assert.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include \"gpu.h\"\n#include \"ps1/gpucmd.h\"\n#include \"ps1/registers.h\"\n\n#define DMA_MAX_CHUNK_SIZE 16\n\nvoid setupGPU(GP1VideoMode mode, int width, int height) {\n\tint x = 0x760;\n\tint y = (mode == GP1_MODE_PAL) ? 0xa3 : 0x88;\n\n\tGP1HorizontalRes horizontalRes = GP1_HRES_320;\n\tGP1VerticalRes   verticalRes   = GP1_VRES_256;\n\n\tint offsetX = (width  * gp1_clockMultiplierH(horizontalRes)) / 2;\n\tint offsetY = (height / gp1_clockDividerV(verticalRes))      / 2;\n\n\tGPU_GP1 = gp1_resetGPU();\n\tGPU_GP1 = gp1_fbRangeH(x - offsetX, x + offsetX);\n\tGPU_GP1 = gp1_fbRangeV(y - offsetY, y + offsetY);\n\tGPU_GP1 = gp1_fbMode(\n\t\thorizontalRes,\n\t\tverticalRes,\n\t\tmode,\n\t\tfalse,\n\t\tGP1_COLOR_16BPP\n\t);\n\tGPU_GP1 = gp1_dispBlank(false);\n\n\tDMA_DPCR         |= DMA_DPCR_CH_ENABLE(DMA_GPU);\n\tDMA_CHCR(DMA_GPU) = 0;\n\n\tGPU_GP1 = gp1_dmaRequestMode(GP1_DREQ_GP0_WRITE);\n}\n\nvoid waitForGP0Ready(void) {\n\twhile (!(GPU_GP1 & GP1_STAT_CMD_READY))\n\t\t__asm__ volatile(\"\");\n}\n\nvoid waitForGPUDMADone(void) {\n\twhile (DMA_CHCR(DMA_GPU) & DMA_CHCR_ENABLE)\n\t\t__asm__ volatile(\"\");\n}\n\nvoid waitForVSync(void) {\n\twhile (!(IRQ_STAT & (1 << IRQ_VSYNC)))\n\t\t__asm__ volatile(\"\");\n\n\tIRQ_STAT = ~(1 << IRQ_VSYNC);\n}\n\nvoid sendGPULinkedList(const void *data) {\n\twaitForGPUDMADone();\n\tassert(!((uint32_t) data % 4));\n\n\tDMA_MADR(DMA_GPU) = (uint32_t) data;\n\tDMA_CHCR(DMA_GPU) = 0\n\t\t| DMA_CHCR_WRITE\n\t\t| DMA_CHCR_MODE_LIST\n\t\t| DMA_CHCR_ENABLE;\n}\n\nvoid sendVRAMData(\n\tconst void *data,\n\tint        x,\n\tint        y,\n\tint        width,\n\tint        height\n) {\n\twaitForGPUDMADone();\n\tassert(!((uint32_t) data % 4));\n\n\tsize_t length = (width * height + 1) / 2;\n\tsize_t chunkSize, numChunks;\n\n\tif (length < DMA_MAX_CHUNK_SIZE) {\n\t\tchunkSize = length;\n\t\tnumChunks = 1;\n\t} else {\n\t\tchunkSize = DMA_MAX_CHUNK_SIZE;\n\t\tnumChunks = length / DMA_MAX_CHUNK_SIZE;\n\n\t\tassert(!(length % DMA_MAX_CHUNK_SIZE));\n\t}\n\n\twaitForGP0Ready();\n\tGPU_GP0 = gp0_vramWrite();\n\tGPU_GP0 = gp0_xy(x, y);\n\tGPU_GP0 = gp0_xy(width, height);\n\n\tDMA_MADR(DMA_GPU) = (uint32_t) data;\n\tDMA_BCR (DMA_GPU) = chunkSize | (numChunks << 16);\n\tDMA_CHCR(DMA_GPU) = 0\n\t\t| DMA_CHCR_WRITE\n\t\t| DMA_CHCR_MODE_SLICE\n\t\t| DMA_CHCR_ENABLE;\n}\n\nuint32_t *allocateGP0Packet(GPUDMAChain *chain, int numCommands) {\n\tassert((numCommands >= 0) && (numCommands <= DMA_MAX_CHUNK_SIZE));\n\n\tuint32_t *ptr      = chain->nextPacket;\n\tchain->nextPacket += numCommands + 1;\n\n\t*ptr = gp0_tag(numCommands, chain->nextPacket);\n\tassert(chain->nextPacket < &(chain->data)[GPU_CHAIN_BUFFER_SIZE]);\n\n\treturn &ptr[1];\n}\n\nvoid uploadTexture(\n\tTextureInfo *info,\n\tconst void  *data,\n\tint         x,\n\tint         y,\n\tint         width,\n\tint         height\n) {\n\tassert((width <= 256) && (height <= 256));\n\n\tsendVRAMData(data, x, y, width, height);\n\twaitForGPUDMADone();\n\tGPU_GP0 = gp0_flushCache();\n\n\tinfo->page   = gp0_page(\n\t\tx /  64,\n\t\ty / 256,\n\t\tGP0_BLEND_SEMITRANS,\n\t\tGP0_COLOR_16BPP\n\t);\n\tinfo->clut   = 0;\n\tinfo->u      = (uint8_t)  (x %  64);\n\tinfo->v      = (uint8_t)  (y % 256);\n\tinfo->width  = (uint16_t) width;\n\tinfo->height = (uint16_t) height;\n}\n\nvoid uploadIndexedTexture(\n\tTextureInfo   *info,\n\tconst void    *image,\n\tconst void    *palette,\n\tint           imageX,\n\tint           imageY,\n\tint           paletteX,\n\tint           paletteY,\n\tint           width,\n\tint           height,\n\tGP0ColorDepth colorDepth\n) {\n\tassert((width <= 256) && (height <= 256));\n\n\tint numColors    = (colorDepth == GP0_COLOR_8BPP) ? 256 : 16;\n\tint widthDivider = (colorDepth == GP0_COLOR_8BPP) ?   2 :  4;\n\n\tassert(!(paletteX % 16) && ((paletteX + numColors) <= 1024));\n\n\tsendVRAMData(image, imageX, imageY, width / widthDivider, height);\n\twaitForGPUDMADone();\n\tsendVRAMData(palette, paletteX, paletteY, numColors, 1);\n\twaitForGPUDMADone();\n\tGPU_GP0 = gp0_flushCache();\n\n\tinfo->page   = gp0_page(\n\t\timageX /  64,\n\t\timageY / 256,\n\t\tGP0_BLEND_SEMITRANS,\n\t\tcolorDepth\n\t);\n\tinfo->clut   = gp0_clut(paletteX / 16, paletteY);\n\tinfo->u      = (uint8_t)  ((imageX % 64) * widthDivider);\n\tinfo->v      = (uint8_t)   (imageY % 256);\n\tinfo->width  = (uint16_t) width;\n\tinfo->height = (uint16_t) height;\n}\n"
  },
  {
    "path": "src/06_fonts/gpu.h",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#pragma once\n\n#include <stdint.h>\n#include \"ps1/gpucmd.h\"\n\n#define GPU_CHAIN_BUFFER_SIZE 1024\n\ntypedef struct {\n\tuint32_t data[GPU_CHAIN_BUFFER_SIZE];\n\tuint32_t *nextPacket;\n} GPUDMAChain;\n\ntypedef struct {\n\tuint8_t  u, v;\n\tuint16_t width, height;\n\tuint16_t page, clut;\n} TextureInfo;\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid setupGPU(GP1VideoMode mode, int width, int height);\nvoid waitForGP0Ready(void);\nvoid waitForGPUDMADone(void);\nvoid waitForVSync(void);\n\nvoid sendGPULinkedList(const void *data);\nvoid sendVRAMData(\n\tconst void *data,\n\tint        x,\n\tint        y,\n\tint        width,\n\tint        height\n);\nuint32_t *allocateGP0Packet(GPUDMAChain *chain, int numCommands);\n\nvoid uploadTexture(\n\tTextureInfo *info,\n\tconst void  *data,\n\tint         x,\n\tint         y,\n\tint         width,\n\tint         height\n);\nvoid uploadIndexedTexture(\n\tTextureInfo   *info,\n\tconst void    *image,\n\tconst void    *palette,\n\tint           imageX,\n\tint           imageY,\n\tint           paletteX,\n\tint           paletteY,\n\tint           width,\n\tint           height,\n\tGP0ColorDepth colorDepth\n);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "src/06_fonts/main.c",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n/*\n * We saw how to load a single texture and display it in the last two examples.\n * Textures, however, are not always simple images displayed in their entirety:\n * sometimes they hold more than one image (e.g. all frames of a character's\n * animation in a 2D game) but are \"cropped out\" on the fly during rendering to\n * only draw a single frame at a time. These textures are known as spritesheets\n * and the PS1's GPU fully supports them, as it allows for arbitrary UV\n * coordinates to be used.\n *\n * This example is going to show how to implement a simple font system for text\n * rendering, since that's one of the most common use cases for spritesheets. We\n * are going to load a single texture containing all our font's characters, as\n * having hundreds of tiny textures for each character would be extremely\n * inefficient, and then use a lookup table to obtain the UV coordinates, width\n * and height of each character in a string.\n *\n * NOTE: in order to make the code easier to read, I have moved all the\n * GPU-related functions from previous examples to a separate source file.\n */\n\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdio.h>\n#include \"gpu.h\"\n#include \"ps1/gpucmd.h\"\n#include \"ps1/registers.h\"\n\n// In order to pick sprites (characters) out of our spritesheet, we need a table\n// listing all of them (in ASCII order in this case) with their UV coordinates\n// within the sheet as well as their dimensions. In this example we're going to\n// hardcode the table, however in an actual game you may want to store this data\n// in the same file as the image and palette data.\ntypedef struct {\n\tuint8_t x, y, width, height;\n} SpriteInfo;\n\nstatic const SpriteInfo fontSprites[] = {\n\t{ .x =  6, .y =  0, .width = 2, .height = 9 }, // !\n\t{ .x = 12, .y =  0, .width = 4, .height = 9 }, // \"\n\t{ .x = 18, .y =  0, .width = 6, .height = 9 }, // #\n\t{ .x = 24, .y =  0, .width = 6, .height = 9 }, // $\n\t{ .x = 30, .y =  0, .width = 6, .height = 9 }, // %\n\t{ .x = 36, .y =  0, .width = 6, .height = 9 }, // &\n\t{ .x = 42, .y =  0, .width = 2, .height = 9 }, // '\n\t{ .x = 48, .y =  0, .width = 3, .height = 9 }, // (\n\t{ .x = 54, .y =  0, .width = 3, .height = 9 }, // )\n\t{ .x = 60, .y =  0, .width = 4, .height = 9 }, // *\n\t{ .x = 66, .y =  0, .width = 6, .height = 9 }, // +\n\t{ .x = 72, .y =  0, .width = 3, .height = 9 }, // ,\n\t{ .x = 78, .y =  0, .width = 6, .height = 9 }, // -\n\t{ .x = 84, .y =  0, .width = 2, .height = 9 }, // .\n\t{ .x = 90, .y =  0, .width = 6, .height = 9 }, // /\n\t{ .x =  0, .y =  9, .width = 6, .height = 9 }, // 0\n\t{ .x =  6, .y =  9, .width = 6, .height = 9 }, // 1\n\t{ .x = 12, .y =  9, .width = 6, .height = 9 }, // 2\n\t{ .x = 18, .y =  9, .width = 6, .height = 9 }, // 3\n\t{ .x = 24, .y =  9, .width = 6, .height = 9 }, // 4\n\t{ .x = 30, .y =  9, .width = 6, .height = 9 }, // 5\n\t{ .x = 36, .y =  9, .width = 6, .height = 9 }, // 6\n\t{ .x = 42, .y =  9, .width = 6, .height = 9 }, // 7\n\t{ .x = 48, .y =  9, .width = 6, .height = 9 }, // 8\n\t{ .x = 54, .y =  9, .width = 6, .height = 9 }, // 9\n\t{ .x = 60, .y =  9, .width = 2, .height = 9 }, // :\n\t{ .x = 66, .y =  9, .width = 3, .height = 9 }, // ;\n\t{ .x = 72, .y =  9, .width = 6, .height = 9 }, // <\n\t{ .x = 78, .y =  9, .width = 6, .height = 9 }, // =\n\t{ .x = 84, .y =  9, .width = 6, .height = 9 }, // >\n\t{ .x = 90, .y =  9, .width = 6, .height = 9 }, // ?\n\t{ .x =  0, .y = 18, .width = 6, .height = 9 }, // @\n\t{ .x =  6, .y = 18, .width = 6, .height = 9 }, // A\n\t{ .x = 12, .y = 18, .width = 6, .height = 9 }, // B\n\t{ .x = 18, .y = 18, .width = 6, .height = 9 }, // C\n\t{ .x = 24, .y = 18, .width = 6, .height = 9 }, // D\n\t{ .x = 30, .y = 18, .width = 6, .height = 9 }, // E\n\t{ .x = 36, .y = 18, .width = 6, .height = 9 }, // F\n\t{ .x = 42, .y = 18, .width = 6, .height = 9 }, // G\n\t{ .x = 48, .y = 18, .width = 6, .height = 9 }, // H\n\t{ .x = 54, .y = 18, .width = 4, .height = 9 }, // I\n\t{ .x = 60, .y = 18, .width = 5, .height = 9 }, // J\n\t{ .x = 66, .y = 18, .width = 6, .height = 9 }, // K\n\t{ .x = 72, .y = 18, .width = 6, .height = 9 }, // L\n\t{ .x = 78, .y = 18, .width = 6, .height = 9 }, // M\n\t{ .x = 84, .y = 18, .width = 6, .height = 9 }, // N\n\t{ .x = 90, .y = 18, .width = 6, .height = 9 }, // O\n\t{ .x =  0, .y = 27, .width = 6, .height = 9 }, // P\n\t{ .x =  6, .y = 27, .width = 6, .height = 9 }, // Q\n\t{ .x = 12, .y = 27, .width = 6, .height = 9 }, // R\n\t{ .x = 18, .y = 27, .width = 6, .height = 9 }, // S\n\t{ .x = 24, .y = 27, .width = 6, .height = 9 }, // T\n\t{ .x = 30, .y = 27, .width = 6, .height = 9 }, // U\n\t{ .x = 36, .y = 27, .width = 6, .height = 9 }, // V\n\t{ .x = 42, .y = 27, .width = 6, .height = 9 }, // W\n\t{ .x = 48, .y = 27, .width = 6, .height = 9 }, // X\n\t{ .x = 54, .y = 27, .width = 6, .height = 9 }, // Y\n\t{ .x = 60, .y = 27, .width = 6, .height = 9 }, // Z\n\t{ .x = 66, .y = 27, .width = 3, .height = 9 }, // [\n\t{ .x = 72, .y = 27, .width = 6, .height = 9 }, // Backslash\n\t{ .x = 78, .y = 27, .width = 3, .height = 9 }, // ]\n\t{ .x = 84, .y = 27, .width = 4, .height = 9 }, // ^\n\t{ .x = 90, .y = 27, .width = 6, .height = 9 }, // _\n\t{ .x =  0, .y = 36, .width = 3, .height = 9 }, // `\n\t{ .x =  6, .y = 36, .width = 6, .height = 9 }, // a\n\t{ .x = 12, .y = 36, .width = 6, .height = 9 }, // b\n\t{ .x = 18, .y = 36, .width = 6, .height = 9 }, // c\n\t{ .x = 24, .y = 36, .width = 6, .height = 9 }, // d\n\t{ .x = 30, .y = 36, .width = 6, .height = 9 }, // e\n\t{ .x = 36, .y = 36, .width = 5, .height = 9 }, // f\n\t{ .x = 42, .y = 36, .width = 6, .height = 9 }, // g\n\t{ .x = 48, .y = 36, .width = 5, .height = 9 }, // h\n\t{ .x = 54, .y = 36, .width = 2, .height = 9 }, // i\n\t{ .x = 60, .y = 36, .width = 4, .height = 9 }, // j\n\t{ .x = 66, .y = 36, .width = 5, .height = 9 }, // k\n\t{ .x = 72, .y = 36, .width = 2, .height = 9 }, // l\n\t{ .x = 78, .y = 36, .width = 6, .height = 9 }, // m\n\t{ .x = 84, .y = 36, .width = 5, .height = 9 }, // n\n\t{ .x = 90, .y = 36, .width = 6, .height = 9 }, // o\n\t{ .x =  0, .y = 45, .width = 6, .height = 9 }, // p\n\t{ .x =  6, .y = 45, .width = 6, .height = 9 }, // q\n\t{ .x = 12, .y = 45, .width = 6, .height = 9 }, // r\n\t{ .x = 18, .y = 45, .width = 6, .height = 9 }, // s\n\t{ .x = 24, .y = 45, .width = 5, .height = 9 }, // t\n\t{ .x = 30, .y = 45, .width = 5, .height = 9 }, // u\n\t{ .x = 36, .y = 45, .width = 6, .height = 9 }, // v\n\t{ .x = 42, .y = 45, .width = 6, .height = 9 }, // w\n\t{ .x = 48, .y = 45, .width = 6, .height = 9 }, // x\n\t{ .x = 54, .y = 45, .width = 6, .height = 9 }, // y\n\t{ .x = 60, .y = 45, .width = 5, .height = 9 }, // z\n\t{ .x = 66, .y = 45, .width = 4, .height = 9 }, // {\n\t{ .x = 72, .y = 45, .width = 2, .height = 9 }, // |\n\t{ .x = 78, .y = 45, .width = 4, .height = 9 }, // }\n\t{ .x = 84, .y = 45, .width = 6, .height = 9 }, // ~\n\t{ .x = 90, .y = 45, .width = 6, .height = 9 }  // Invalid character\n};\n\n#define FONT_FIRST_TABLE_CHAR '!'\n#define FONT_SPACE_WIDTH       4\n#define FONT_TAB_WIDTH        32\n#define FONT_LINE_HEIGHT      10\n\nstatic void printString(\n\tGPUDMAChain       *chain,\n\tconst TextureInfo *font,\n\tint               x,\n\tint               y,\n\tconst char        *str\n) {\n\tint currentX = x, currentY = y;\n\n\tuint32_t *ptr;\n\n\t// Start by sending a texture page command to tell the GPU to use the font's\n\t// spritesheet. The page setting persists when drawing rectangles, so\n\t// sending it here just once is enough.\n\tptr    = allocateGP0Packet(chain, 1);\n\tptr[0] = gp0_setPage(font->page, false, false);\n\n\t// Iterate over every character in the string.\n\tfor (; *str; str++) {\n\t\tchar ch = *str;\n\n\t\t// Check if the character is \"special\" and shall be handled without\n\t\t// drawing any sprite, or if it's invalid and should be rendered as a\n\t\t// box with a question mark (character code 127).\n\t\tswitch (ch) {\n\t\t\tcase '\\t':\n\t\t\t\tcurrentX += FONT_TAB_WIDTH - 1;\n\t\t\t\tcurrentX -= currentX % FONT_TAB_WIDTH;\n\t\t\t\tcontinue;\n\n\t\t\tcase '\\n':\n\t\t\t\tcurrentX  = x;\n\t\t\t\tcurrentY += FONT_LINE_HEIGHT;\n\t\t\t\tcontinue;\n\n\t\t\tcase ' ':\n\t\t\t\tcurrentX += FONT_SPACE_WIDTH;\n\t\t\t\tcontinue;\n\n\t\t\tcase '\\x80' ... '\\xff':\n\t\t\t\tch = '\\x7f';\n\t\t\t\tbreak;\n\t\t}\n\n\t\t// If the character was not a tab, newline or space, fetch its\n\t\t// respective entry from the sprite coordinate table.\n\t\tconst SpriteInfo *sprite = &fontSprites[ch - FONT_FIRST_TABLE_CHAR];\n\n\t\t// Draw the character, summing the UV coordinates of the spritesheet in\n\t\t// VRAM to those of the sprite itself within the sheet. Enable blending\n\t\t// to make sure any semitransparent pixels in the font get rendered\n\t\t// correctly.\n\t\tptr    = allocateGP0Packet(chain, 4);\n\t\tptr[0] = gp0_rectangle(true, true, true);\n\t\tptr[1] = gp0_xy(currentX, currentY);\n\t\tptr[2] = gp0_uv(font->u + sprite->x, font->v + sprite->y, font->clut);\n\t\tptr[3] = gp0_xy(sprite->width, sprite->height);\n\n\t\t// Move onto the next character.\n\t\tcurrentX += sprite->width;\n\t}\n}\n\n#define SCREEN_WIDTH     320\n#define SCREEN_HEIGHT    240\n#define FONT_WIDTH        96\n#define FONT_HEIGHT       56\n#define FONT_COLOR_DEPTH GP0_COLOR_4BPP\n\nextern const uint8_t fontTexture[], fontPalette[];\n\nint main(int argc, const char **argv) {\n\tinitSerialIO(115200);\n\n\tif ((GPU_GP1 & GP1_STAT_FB_MODE_BITMASK) == GP1_STAT_FB_MODE_PAL) {\n\t\tputs(\"Using PAL mode\");\n\t\tsetupGPU(GP1_MODE_PAL, SCREEN_WIDTH, SCREEN_HEIGHT);\n\t} else {\n\t\tputs(\"Using NTSC mode\");\n\t\tsetupGPU(GP1_MODE_NTSC, SCREEN_WIDTH, SCREEN_HEIGHT);\n\t}\n\n\tTextureInfo font;\n\n\tuploadIndexedTexture(\n\t\t&font,\n\t\tfontTexture,\n\t\tfontPalette,\n\t\tSCREEN_WIDTH * 2,\n\t\t0,\n\t\tSCREEN_WIDTH * 2,\n\t\tFONT_HEIGHT,\n\t\tFONT_WIDTH,\n\t\tFONT_HEIGHT,\n\t\tFONT_COLOR_DEPTH\n\t);\n\n\tGPUDMAChain dmaChains[2];\n\tbool        usingSecondFrame = false;\n\tint         frameCounter     = 0;\n\n\tfor (;;) {\n\t\tint bufferX = usingSecondFrame ? SCREEN_WIDTH : 0;\n\t\tint bufferY = 0;\n\n\t\tGPUDMAChain *chain = &dmaChains[usingSecondFrame];\n\t\tusingSecondFrame   = !usingSecondFrame;\n\n\t\tuint32_t *ptr;\n\n\t\tGPU_GP1 = gp1_fbOffset(bufferX, bufferY);\n\n\t\tchain->nextPacket = chain->data;\n\n\t\tptr    = allocateGP0Packet(chain, 4);\n\t\tptr[0] = gp0_setPage(0, true, false);\n\t\tptr[1] = gp0_fbOffset1(bufferX, bufferY);\n\t\tptr[2] = gp0_fbOffset2(\n\t\t\tbufferX + SCREEN_WIDTH  - 1,\n\t\t\tbufferY + SCREEN_HEIGHT - 1\n\t\t);\n\t\tptr[3] = gp0_fbOrigin(bufferX, bufferY);\n\n\t\tptr    = allocateGP0Packet(chain, 3);\n\t\tptr[0] = gp0_rgb(64, 64, 64) | gp0_vramFill();\n\t\tptr[1] = gp0_xy(bufferX, bufferY);\n\t\tptr[2] = gp0_xy(SCREEN_WIDTH, SCREEN_HEIGHT);\n\n\t\tprintString(\n\t\t\tchain,\n\t\t\t&font,\n\t\t\t16,\n\t\t\t32,\n\t\t\t\"Hello world!\\n\"\n\t\t\t\"We're printing text using nothing but our font spritesheet.\"\n\t\t);\n\n\t\t// Show the current frame number by formatting some text into a\n\t\t// temporary buffer then printing it.\n\t\tchar buffer[32];\n\n\t\tsnprintf(buffer, sizeof(buffer), \"Current frame: %d\", frameCounter++);\n\t\tprintString(chain, &font, 16, 64, buffer);\n\n\t\t*(chain->nextPacket) = gp0_endTag(0);\n\n\t\twaitForGP0Ready();\n\t\twaitForVSync();\n\t\tsendGPULinkedList(chain->data);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/07_orderingTable/gpu.c",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#include <assert.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include \"gpu.h\"\n#include \"ps1/gpucmd.h\"\n#include \"ps1/registers.h\"\n\n#define DMA_MAX_CHUNK_SIZE 16\n\nvoid setupGPU(GP1VideoMode mode, int width, int height) {\n\tint x = 0x760;\n\tint y = (mode == GP1_MODE_PAL) ? 0xa3 : 0x88;\n\n\tGP1HorizontalRes horizontalRes = GP1_HRES_320;\n\tGP1VerticalRes   verticalRes   = GP1_VRES_256;\n\n\tint offsetX = (width  * gp1_clockMultiplierH(horizontalRes)) / 2;\n\tint offsetY = (height / gp1_clockDividerV(verticalRes))      / 2;\n\n\tGPU_GP1 = gp1_resetGPU();\n\tGPU_GP1 = gp1_fbRangeH(x - offsetX, x + offsetX);\n\tGPU_GP1 = gp1_fbRangeV(y - offsetY, y + offsetY);\n\tGPU_GP1 = gp1_fbMode(\n\t\thorizontalRes,\n\t\tverticalRes,\n\t\tmode,\n\t\tfalse,\n\t\tGP1_COLOR_16BPP\n\t);\n\tGPU_GP1 = gp1_dispBlank(false);\n\n\t// Enable and reset the OTC DMA channel in addition to the GPU one.\n\tDMA_DPCR         |= 0\n\t\t| DMA_DPCR_CH_ENABLE(DMA_GPU)\n\t\t| DMA_DPCR_CH_ENABLE(DMA_OTC);\n\tDMA_CHCR(DMA_GPU) = 0;\n\tDMA_CHCR(DMA_OTC) = 0;\n\n\tGPU_GP1 = gp1_dmaRequestMode(GP1_DREQ_GP0_WRITE);\n}\n\nvoid waitForGP0Ready(void) {\n\twhile (!(GPU_GP1 & GP1_STAT_CMD_READY))\n\t\t__asm__ volatile(\"\");\n}\n\nvoid waitForGPUDMADone(void) {\n\twhile (DMA_CHCR(DMA_GPU) & DMA_CHCR_ENABLE)\n\t\t__asm__ volatile(\"\");\n}\n\nvoid waitForVSync(void) {\n\twhile (!(IRQ_STAT & (1 << IRQ_VSYNC)))\n\t\t__asm__ volatile(\"\");\n\n\tIRQ_STAT = ~(1 << IRQ_VSYNC);\n}\n\nvoid sendGPULinkedList(const void *data) {\n\twaitForGPUDMADone();\n\tassert(!((uint32_t) data % 4));\n\n\tDMA_MADR(DMA_GPU) = (uint32_t) data;\n\tDMA_CHCR(DMA_GPU) = 0\n\t\t| DMA_CHCR_WRITE\n\t\t| DMA_CHCR_MODE_LIST\n\t\t| DMA_CHCR_ENABLE;\n}\n\nvoid sendVRAMData(\n\tconst void *data,\n\tint        x,\n\tint        y,\n\tint        width,\n\tint        height\n) {\n\twaitForGPUDMADone();\n\tassert(!((uint32_t) data % 4));\n\n\tsize_t length = (width * height + 1) / 2;\n\tsize_t chunkSize, numChunks;\n\n\tif (length < DMA_MAX_CHUNK_SIZE) {\n\t\tchunkSize = length;\n\t\tnumChunks = 1;\n\t} else {\n\t\tchunkSize = DMA_MAX_CHUNK_SIZE;\n\t\tnumChunks = length / DMA_MAX_CHUNK_SIZE;\n\n\t\tassert(!(length % DMA_MAX_CHUNK_SIZE));\n\t}\n\n\twaitForGP0Ready();\n\tGPU_GP0 = gp0_vramWrite();\n\tGPU_GP0 = gp0_xy(x, y);\n\tGPU_GP0 = gp0_xy(width, height);\n\n\tDMA_MADR(DMA_GPU) = (uint32_t) data;\n\tDMA_BCR (DMA_GPU) = chunkSize | (numChunks << 16);\n\tDMA_CHCR(DMA_GPU) = 0\n\t\t| DMA_CHCR_WRITE\n\t\t| DMA_CHCR_MODE_SLICE\n\t\t| DMA_CHCR_ENABLE;\n}\n\nvoid clearOrderingTable(uint32_t *table, int numEntries) {\n\t// Set up the OTC DMA channel to transfer a new empty ordering table to RAM.\n\t// The table is always reversed and generated \"backwards\" (the last item in\n\t// the table is the first one that will be written), so we must give DMA a\n\t// pointer to the end of the table rather than its beginning.\n\tDMA_MADR(DMA_OTC) = (uint32_t) &table[numEntries - 1];\n\tDMA_BCR (DMA_OTC) = numEntries;\n\tDMA_CHCR(DMA_OTC) = 0\n\t\t| DMA_CHCR_READ\n\t\t| DMA_CHCR_REVERSE\n\t\t| DMA_CHCR_MODE_BURST\n\t\t| DMA_CHCR_ENABLE\n\t\t| DMA_CHCR_TRIGGER;\n\n\t// Wait for DMA to finish generating the table.\n\twhile (DMA_CHCR(DMA_OTC) & DMA_CHCR_ENABLE)\n\t\t__asm__ volatile(\"\");\n}\n\n// As we're using an ordering table, allocateGP0Packet() now takes the packet's\n// Z index (i.e. the index of the \"bucket\" to link it to) as an argument. The\n// table is reversed, so packets with higher Z values will be drawn first and\n// between two packets with the same Z index the most recently added one will\n// take precedence.\nuint32_t *allocateGP0Packet(GPUDMAChain *chain, int zIndex, int numCommands) {\n\t// Ensure both the packet length and index are within valid range.\n\tassert((numCommands >= 0) && (numCommands <= DMA_MAX_CHUNK_SIZE));\n\tassert((zIndex      >= 0) && (zIndex      <  GPU_ORDERING_TABLE_SIZE));\n\n\tuint32_t *ptr      = chain->nextPacket;\n\tchain->nextPacket += numCommands + 1;\n\n\t// Splice the new packet into the ordering table by:\n\t// - taking the address the ordering table entry currently points to;\n\t// - replacing that address with a pointer to the packet;\n\t// - linking the packet to the old address.\n\t*ptr = gp0_tag(numCommands, (void *) chain->orderingTable[zIndex]);\n\tchain->orderingTable[zIndex] = gp0_tag(0, ptr);\n\n\tassert(chain->nextPacket < &(chain->data)[GPU_CHAIN_BUFFER_SIZE]);\n\n\treturn &ptr[1];\n}\n\nvoid uploadTexture(\n\tTextureInfo *info,\n\tconst void  *data,\n\tint         x,\n\tint         y,\n\tint         width,\n\tint         height\n) {\n\tassert((width <= 256) && (height <= 256));\n\n\tsendVRAMData(data, x, y, width, height);\n\twaitForGPUDMADone();\n\tGPU_GP0 = gp0_flushCache();\n\n\tinfo->page   = gp0_page(\n\t\tx /  64,\n\t\ty / 256,\n\t\tGP0_BLEND_SEMITRANS,\n\t\tGP0_COLOR_16BPP\n\t);\n\tinfo->clut   = 0;\n\tinfo->u      = (uint8_t)  (x % 64);\n\tinfo->v      = (uint8_t)  (y % 256);\n\tinfo->width  = (uint16_t) width;\n\tinfo->height = (uint16_t) height;\n}\n\nvoid uploadIndexedTexture(\n\tTextureInfo   *info,\n\tconst void    *image,\n\tconst void    *palette,\n\tint           imageX,\n\tint           imageY,\n\tint           paletteX,\n\tint           paletteY,\n\tint           width,\n\tint           height,\n\tGP0ColorDepth colorDepth\n) {\n\tassert((width <= 256) && (height <= 256));\n\n\tint numColors    = (colorDepth == GP0_COLOR_8BPP) ? 256 : 16;\n\tint widthDivider = (colorDepth == GP0_COLOR_8BPP) ?   2 :  4;\n\n\tassert(!(paletteX % 16) && ((paletteX + numColors) <= 1024));\n\n\tsendVRAMData(image, imageX, imageY, width / widthDivider, height);\n\twaitForGPUDMADone();\n\tsendVRAMData(palette, paletteX, paletteY, numColors, 1);\n\twaitForGPUDMADone();\n\tGPU_GP0 = gp0_flushCache();\n\n\tinfo->page   = gp0_page(\n\t\timageX /  64,\n\t\timageY / 256,\n\t\tGP0_BLEND_SEMITRANS,\n\t\tcolorDepth\n\t);\n\tinfo->clut   = gp0_clut(paletteX / 16, paletteY);\n\tinfo->u      = (uint8_t)  ((imageX %  64) * widthDivider);\n\tinfo->v      = (uint8_t)   (imageY % 256);\n\tinfo->width  = (uint16_t) width;\n\tinfo->height = (uint16_t) height;\n}\n"
  },
  {
    "path": "src/07_orderingTable/gpu.h",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#pragma once\n\n#include <stdint.h>\n#include \"ps1/gpucmd.h\"\n\n// We are going to store the ordering table for each frame as part of the DMA\n// chain structure. We'll have 32 different \"buckets\" and thus Z indices at our\n// disposal.\n#define GPU_CHAIN_BUFFER_SIZE   1024\n#define GPU_ORDERING_TABLE_SIZE   32\n\ntypedef struct {\n\tuint32_t data[GPU_CHAIN_BUFFER_SIZE];\n\tuint32_t orderingTable[GPU_ORDERING_TABLE_SIZE];\n\tuint32_t *nextPacket;\n} GPUDMAChain;\n\ntypedef struct {\n\tuint8_t  u, v;\n\tuint16_t width, height;\n\tuint16_t page, clut;\n} TextureInfo;\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid setupGPU(GP1VideoMode mode, int width, int height);\nvoid waitForGP0Ready(void);\nvoid waitForGPUDMADone(void);\nvoid waitForVSync(void);\n\nvoid sendGPULinkedList(const void *data);\nvoid sendVRAMData(\n\tconst void *data,\n\tint        x,\n\tint        y,\n\tint        width,\n\tint        height\n);\nvoid clearOrderingTable(uint32_t *table, int numEntries);\nuint32_t *allocateGP0Packet(GPUDMAChain *chain, int zIndex, int numCommands);\n\nvoid uploadTexture(\n\tTextureInfo *info,\n\tconst void  *data,\n\tint         x,\n\tint         y,\n\tint         width,\n\tint         height\n);\nvoid uploadIndexedTexture(\n\tTextureInfo   *info,\n\tconst void    *image,\n\tconst void    *palette,\n\tint           imageX,\n\tint           imageY,\n\tint           paletteX,\n\tint           paletteY,\n\tint           width,\n\tint           height,\n\tGP0ColorDepth colorDepth\n);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "src/07_orderingTable/main.c",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n/*\n * We have seen pretty much every core feature of the PS1's GPU at this point.\n * However, an important piece of functionality is still missing: we do not know\n * how to control the order the GPU processes our linked list packets in. This\n * may not sound particularly useful for 2D graphics but is crucial for 3D, as\n * we'll have to sort our polygons by distance to make sure items closest to the\n * camera are drawn last (the GPU has no depth buffer to help with this).\n *\n * Fortunately, linked lists lend themselves well to manipulation and sorting.\n * The DMA unit, which we've only used for its GPU channel so far, includes\n * another channel known as OTC, which can quickly generate a series of empty\n * (header-only) GPU DMA packets linked to each other and write them to RAM.\n * These packets will form what's known as an ordering table, a chain of dummy\n * packets whose purpose is to serve as \"anchor points\" for other packets to be\n * linked to. By having an ordering table with N items it is thus possible to\n * have N different \"buckets\" to sort packets into, with the ordering table\n * linking all buckets together and making sure the GPU draws them in order.\n */\n\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdio.h>\n#include \"gpu.h\"\n#include \"ps1/gpucmd.h\"\n#include \"ps1/registers.h\"\n\n#define SCREEN_WIDTH  320\n#define SCREEN_HEIGHT 240\n\nint main(int argc, const char **argv) {\n\tinitSerialIO(115200);\n\n\tif ((GPU_GP1 & GP1_STAT_FB_MODE_BITMASK) == GP1_STAT_FB_MODE_PAL) {\n\t\tputs(\"Using PAL mode\");\n\t\tsetupGPU(GP1_MODE_PAL, SCREEN_WIDTH, SCREEN_HEIGHT);\n\t} else {\n\t\tputs(\"Using NTSC mode\");\n\t\tsetupGPU(GP1_MODE_NTSC, SCREEN_WIDTH, SCREEN_HEIGHT);\n\t}\n\n\tGPUDMAChain dmaChains[2];\n\tbool        usingSecondFrame = false;\n\tint         frameCounter     = 0;\n\n\tfor (;;) {\n\t\tint bufferX = usingSecondFrame ? SCREEN_WIDTH : 0;\n\t\tint bufferY = 0;\n\n\t\tGPUDMAChain *chain = &dmaChains[usingSecondFrame];\n\t\tusingSecondFrame   = !usingSecondFrame;\n\n\t\tuint32_t *ptr;\n\n\t\tGPU_GP1 = gp1_fbOffset(bufferX, bufferY);\n\n\t\t// Reset the ordering table to a blank state.\n\t\tclearOrderingTable(chain->orderingTable, GPU_ORDERING_TABLE_SIZE);\n\t\tchain->nextPacket = chain->data;\n\n\t\t// Draw 16 stacked squares, animating their Z indices. The packets are\n\t\t// always allocated in the same order (top left to bottom right square),\n\t\t// but the table will reorder them as they are sent to the GPU.\n\t\tint x = 16, y = 24;\n\n\t\tint frontSquareIndex = (frameCounter++ / 10) % 16;\n\n\t\tfor (int i = 0; i < 16; i++) {\n\t\t\tuint32_t color = gp0_rgb(i * 15, i * 15, 0);\n\t\t\tint      zIndex;\n\n\t\t\tif (i < frontSquareIndex)\n\t\t\t\tzIndex = frontSquareIndex - i;\n\t\t\telse\n\t\t\t\tzIndex = i - frontSquareIndex;\n\n\t\t\tptr    = allocateGP0Packet(chain, zIndex, 3);\n\t\t\tptr[0] = color | gp0_rectangle(false, false, false);\n\t\t\tptr[1] = gp0_xy(x, y);\n\t\t\tptr[2] = gp0_xy(32, 32);\n\n\t\t\tx += 16;\n\t\t\ty += 10;\n\t\t}\n\n\t\t// Place the framebuffer offset and screen clearing commands last, as\n\t\t// the \"furthest away\" items in the table. Since the ordering table is\n\t\t// reversed (see the allocateGP0Packet() note), this ensures they'll be\n\t\t// executed first.\n\t\tptr    = allocateGP0Packet(chain, GPU_ORDERING_TABLE_SIZE - 1, 3);\n\t\tptr[0] = gp0_rgb(64, 64, 64) | gp0_vramFill();\n\t\tptr[1] = gp0_xy(bufferX, bufferY);\n\t\tptr[2] = gp0_xy(SCREEN_WIDTH, SCREEN_HEIGHT);\n\n\t\tptr    = allocateGP0Packet(chain, GPU_ORDERING_TABLE_SIZE - 1, 4);\n\t\tptr[0] = gp0_setPage(0, true, false);\n\t\tptr[1] = gp0_fbOffset1(bufferX, bufferY);\n\t\tptr[2] = gp0_fbOffset2(\n\t\t\tbufferX + SCREEN_WIDTH  - 1,\n\t\t\tbufferY + SCREEN_HEIGHT - 1\n\t\t);\n\t\tptr[3] = gp0_fbOrigin(bufferX, bufferY);\n\n\t\t// Give DMA a pointer to the first (last) entry in the table. There is\n\t\t// no need to terminate the table manually as the OTC DMA channel\n\t\t// already inserts a terminator packet.\n\t\twaitForGP0Ready();\n\t\twaitForVSync();\n\t\tsendGPULinkedList(&(chain->orderingTable)[GPU_ORDERING_TABLE_SIZE - 1]);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/08_spinningCube/gpu.c",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#include <assert.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include \"gpu.h\"\n#include \"ps1/gpucmd.h\"\n#include \"ps1/registers.h\"\n\n#define DMA_MAX_CHUNK_SIZE 16\n\nvoid setupGPU(GP1VideoMode mode, int width, int height) {\n\tint x = 0x760;\n\tint y = (mode == GP1_MODE_PAL) ? 0xa3 : 0x88;\n\n\tGP1HorizontalRes horizontalRes = GP1_HRES_320;\n\tGP1VerticalRes   verticalRes   = GP1_VRES_256;\n\n\tint offsetX = (width  * gp1_clockMultiplierH(horizontalRes)) / 2;\n\tint offsetY = (height / gp1_clockDividerV(verticalRes))      / 2;\n\n\tGPU_GP1 = gp1_resetGPU();\n\tGPU_GP1 = gp1_fbRangeH(x - offsetX, x + offsetX);\n\tGPU_GP1 = gp1_fbRangeV(y - offsetY, y + offsetY);\n\tGPU_GP1 = gp1_fbMode(\n\t\thorizontalRes,\n\t\tverticalRes,\n\t\tmode,\n\t\tfalse,\n\t\tGP1_COLOR_16BPP\n\t);\n\tGPU_GP1 = gp1_dispBlank(false);\n\n\tDMA_DPCR         |= 0\n\t\t| DMA_DPCR_CH_ENABLE(DMA_GPU)\n\t\t| DMA_DPCR_CH_ENABLE(DMA_OTC);\n\tDMA_CHCR(DMA_GPU) = 0;\n\tDMA_CHCR(DMA_OTC) = 0;\n\n\tGPU_GP1 = gp1_dmaRequestMode(GP1_DREQ_GP0_WRITE);\n}\n\nvoid waitForGP0Ready(void) {\n\twhile (!(GPU_GP1 & GP1_STAT_CMD_READY))\n\t\t__asm__ volatile(\"\");\n}\n\nvoid waitForGPUDMADone(void) {\n\twhile (DMA_CHCR(DMA_GPU) & DMA_CHCR_ENABLE)\n\t\t__asm__ volatile(\"\");\n}\n\nvoid waitForVSync(void) {\n\twhile (!(IRQ_STAT & (1 << IRQ_VSYNC)))\n\t\t__asm__ volatile(\"\");\n\n\tIRQ_STAT = ~(1 << IRQ_VSYNC);\n}\n\nvoid sendGPULinkedList(const void *data) {\n\twaitForGPUDMADone();\n\tassert(!((uint32_t) data % 4));\n\n\tDMA_MADR(DMA_GPU) = (uint32_t) data;\n\tDMA_CHCR(DMA_GPU) = 0\n\t\t| DMA_CHCR_WRITE\n\t\t| DMA_CHCR_MODE_LIST\n\t\t| DMA_CHCR_ENABLE;\n}\n\nvoid sendVRAMData(\n\tconst void *data,\n\tint        x,\n\tint        y,\n\tint        width,\n\tint        height\n) {\n\twaitForGPUDMADone();\n\tassert(!((uint32_t) data % 4));\n\n\tsize_t length = (width * height + 1) / 2;\n\tsize_t chunkSize, numChunks;\n\n\tif (length < DMA_MAX_CHUNK_SIZE) {\n\t\tchunkSize = length;\n\t\tnumChunks = 1;\n\t} else {\n\t\tchunkSize = DMA_MAX_CHUNK_SIZE;\n\t\tnumChunks = length / DMA_MAX_CHUNK_SIZE;\n\n\t\tassert(!(length % DMA_MAX_CHUNK_SIZE));\n\t}\n\n\twaitForGP0Ready();\n\tGPU_GP0 = gp0_vramWrite();\n\tGPU_GP0 = gp0_xy(x, y);\n\tGPU_GP0 = gp0_xy(width, height);\n\n\tDMA_MADR(DMA_GPU) = (uint32_t) data;\n\tDMA_BCR (DMA_GPU) = chunkSize | (numChunks << 16);\n\tDMA_CHCR(DMA_GPU) = 0\n\t\t| DMA_CHCR_WRITE\n\t\t| DMA_CHCR_MODE_SLICE\n\t\t| DMA_CHCR_ENABLE;\n}\n\nvoid clearOrderingTable(uint32_t *table, int numEntries) {\n\tDMA_MADR(DMA_OTC) = (uint32_t) &table[numEntries - 1];\n\tDMA_BCR (DMA_OTC) = numEntries;\n\tDMA_CHCR(DMA_OTC) = 0\n\t\t| DMA_CHCR_READ\n\t\t| DMA_CHCR_REVERSE\n\t\t| DMA_CHCR_MODE_BURST\n\t\t| DMA_CHCR_ENABLE\n\t\t| DMA_CHCR_TRIGGER;\n\n\twhile (DMA_CHCR(DMA_OTC) & DMA_CHCR_ENABLE)\n\t\t__asm__ volatile(\"\");\n}\n\nuint32_t *allocateGP0Packet(GPUDMAChain *chain, int zIndex, int numCommands) {\n\tassert((numCommands >= 0) && (numCommands <= DMA_MAX_CHUNK_SIZE));\n\tassert((zIndex      >= 0) && (zIndex      <  GPU_ORDERING_TABLE_SIZE));\n\n\tuint32_t *ptr      = chain->nextPacket;\n\tchain->nextPacket += numCommands + 1;\n\n\t*ptr = gp0_tag(numCommands, (void *) chain->orderingTable[zIndex]);\n\tchain->orderingTable[zIndex] = gp0_tag(0, ptr);\n\n\tassert(chain->nextPacket < &(chain->data)[GPU_CHAIN_BUFFER_SIZE]);\n\n\treturn &ptr[1];\n}\n\nvoid uploadTexture(\n\tTextureInfo *info,\n\tconst void  *data,\n\tint         x,\n\tint         y,\n\tint         width,\n\tint         height\n) {\n\tassert((width <= 256) && (height <= 256));\n\n\tsendVRAMData(data, x, y, width, height);\n\twaitForGPUDMADone();\n\tGPU_GP0 = gp0_flushCache();\n\n\tinfo->page   = gp0_page(\n\t\tx /  64,\n\t\ty / 256,\n\t\tGP0_BLEND_SEMITRANS,\n\t\tGP0_COLOR_16BPP\n\t);\n\tinfo->clut   = 0;\n\tinfo->u      = (uint8_t)  (x %  64);\n\tinfo->v      = (uint8_t)  (y % 256);\n\tinfo->width  = (uint16_t) width;\n\tinfo->height = (uint16_t) height;\n}\n\nvoid uploadIndexedTexture(\n\tTextureInfo   *info,\n\tconst void    *image,\n\tconst void    *palette,\n\tint           imageX,\n\tint           imageY,\n\tint           paletteX,\n\tint           paletteY,\n\tint           width,\n\tint           height,\n\tGP0ColorDepth colorDepth\n) {\n\tassert((width <= 256) && (height <= 256));\n\n\tint numColors    = (colorDepth == GP0_COLOR_8BPP) ? 256 : 16;\n\tint widthDivider = (colorDepth == GP0_COLOR_8BPP) ?   2 :  4;\n\n\tassert(!(paletteX % 16) && ((paletteX + numColors) <= 1024));\n\n\tsendVRAMData(image, imageX, imageY, width / widthDivider, height);\n\twaitForGPUDMADone();\n\tsendVRAMData(palette, paletteX, paletteY, numColors, 1);\n\twaitForGPUDMADone();\n\tGPU_GP0 = gp0_flushCache();\n\n\tinfo->page   = gp0_page(\n\t\timageX /  64,\n\t\timageY / 256,\n\t\tGP0_BLEND_SEMITRANS,\n\t\tcolorDepth\n\t);\n\tinfo->clut   = gp0_clut(paletteX / 16, paletteY);\n\tinfo->u      = (uint8_t)  ((imageX %  64) * widthDivider);\n\tinfo->v      = (uint8_t)   (imageY % 256);\n\tinfo->width  = (uint16_t) width;\n\tinfo->height = (uint16_t) height;\n}\n"
  },
  {
    "path": "src/08_spinningCube/gpu.h",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#pragma once\n\n#include <stdint.h>\n#include \"ps1/gpucmd.h\"\n\n// In order for Z averaging to work properly, ORDERING_TABLE_SIZE should be set\n// to either a relatively high value (1024 or more) or a multiple of 12; see\n// setupGTE() for more details. Higher values will take up more memory but are\n// required to render more complex scenes with wide depth ranges correctly.\n#define GPU_CHAIN_BUFFER_SIZE   1024\n#define GPU_ORDERING_TABLE_SIZE  240\n\ntypedef struct {\n\tuint32_t data[GPU_CHAIN_BUFFER_SIZE];\n\tuint32_t orderingTable[GPU_ORDERING_TABLE_SIZE];\n\tuint32_t *nextPacket;\n} GPUDMAChain;\n\ntypedef struct {\n\tuint8_t  u, v;\n\tuint16_t width, height;\n\tuint16_t page, clut;\n} TextureInfo;\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid setupGPU(GP1VideoMode mode, int width, int height);\nvoid waitForGP0Ready(void);\nvoid waitForGPUDMADone(void);\nvoid waitForVSync(void);\n\nvoid sendGPULinkedList(const void *data);\nvoid sendVRAMData(\n\tconst void *data,\n\tint        x,\n\tint        y,\n\tint        width,\n\tint        height\n);\nvoid clearOrderingTable(uint32_t *table, int numEntries);\nuint32_t *allocateGP0Packet(GPUDMAChain *chain, int zIndex, int numCommands);\n\nvoid uploadTexture(\n\tTextureInfo *info,\n\tconst void  *data,\n\tint         x,\n\tint         y,\n\tint         width,\n\tint         height\n);\nvoid uploadIndexedTexture(\n\tTextureInfo   *info,\n\tconst void    *image,\n\tconst void    *palette,\n\tint           imageX,\n\tint           imageY,\n\tint           paletteX,\n\tint           paletteY,\n\tint           width,\n\tint           height,\n\tGP0ColorDepth colorDepth\n);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "src/08_spinningCube/main.c",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n/*\n * Having explored the capabilities of the PS1's GPU in previous examples, it is\n * now time to focus on the other piece of hardware that makes 3D graphics on\n * the PS1 possible: the geometry transformation engine (GTE), a specialized\n * coprocessor whose job is to perform various geometry-related calculations\n * much faster than the CPU could on its own. To draw a 3D scene the CPU can use\n * the GTE to calculate the screen space coordinates of each polygon's vertices,\n * then pack those into a display list which will be sent off to the GPU for\n * drawing. In this example we're going to draw a spinning model of a cube,\n * using the GTE to carry out the computationally heavy tasks of rotation and\n * perspective projection.\n *\n * Unlike any other peripheral on the console, the GTE is not memory-mapped\n * but rather accessed through special CPU instructions that require the use of\n * inline assembly. This tutorial will thus use the cop0.h and gte.h headers I\n * wrote to abstract away the low-level assembly required to access GTE\n * registers, focusing on its practical usage instead. This example may be\n * harder to follow compared to previous ones for people unfamiliar with basic\n * linear algebra and 3D geometry concepts, so familiarizing with those is\n * highly recommended.\n */\n\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdio.h>\n#include \"gpu.h\"\n#include \"ps1/cop0.h\"\n#include \"ps1/gpucmd.h\"\n#include \"ps1/gte.h\"\n#include \"ps1/registers.h\"\n#include \"trig.h\"\n\n// The GTE uses a 20.12 fixed-point format for most values. What this means is\n// that fractional values will be stored as integers by multiplying them by a\n// fixed unit, in this case 4096 or 1 << 12 (hence making the fractional part 12\n// bits long). We'll define this unit value to make their handling easier.\n#define GTE_UNIT (1 << 12)\n\nstatic void setupGTE(int width, int height) {\n\t// Ensure the GTE, which is coprocessor 2, is enabled. MIPS coprocessors are\n\t// enabled through the status register in coprocessor 0, which is always\n\t// accessible.\n\tcop0_setReg(COP0_STATUS, cop0_getReg(COP0_STATUS) | COP0_STATUS_CU2);\n\n\t// Set the offset to be added to all calculated screen space coordinates (we\n\t// want our cube to appear at the center of the screen) Note that OFX and\n\t// OFY are 16.16 fixed-point rather than 20.12.\n\tgte_setControlReg(GTE_OFX, (width  << 16) / 2);\n\tgte_setControlReg(GTE_OFY, (height << 16) / 2);\n\n\t// Set the distance of the perspective projection plane (i.e. the camera's\n\t// focal length), which affects the field of view.\n\tint focalLength = (width < height) ? width : height;\n\n\tgte_setControlReg(GTE_H, focalLength / 2);\n\n\t// Set the scaling factor for Z averaging. For each polygon drawn, the GTE\n\t// will sum the transformed Z coordinates of its vertices multiplied by this\n\t// value in order to derive the ordering table bucket index the polygon will\n\t// be sorted into. This will work best if the ordering table length is a\n\t// multiple of 12 (i.e. both 3 and 4) or high enough to make any rounding\n\t// error negligible.\n\tgte_setControlReg(GTE_ZSF3, GPU_ORDERING_TABLE_SIZE / 3);\n\tgte_setControlReg(GTE_ZSF4, GPU_ORDERING_TABLE_SIZE / 4);\n}\n\n// When transforming vertices, the GTE will multiply their vectors by a 3x3\n// matrix stored in its registers. This matrix can be used, among other things,\n// to rotate the model by multiplying it by the appropriate rotation matrices.\n// The two functions below handle manipulation of this matrix.\nstatic void multiplyCurrentMatrixByVectors(GTEMatrix *output) {\n\t// Multiply the GTE's current matrix by the matrix whose column vectors are\n\t// V0/V1/V2, then store the result to the provided location. This has to be\n\t// done one column at a time, as the GTE only supports multiplying a matrix\n\t// by a vector using the MVMVA command.\n\tgte_command(GTE_CMD_MVMVA | GTE_SF | GTE_MX_RT | GTE_V_V0 | GTE_CV_NONE);\n\toutput->values[0][0] = (int16_t) gte_getDataReg(GTE_IR1);\n\toutput->values[1][0] = (int16_t) gte_getDataReg(GTE_IR2);\n\toutput->values[2][0] = (int16_t) gte_getDataReg(GTE_IR3);\n\n\tgte_command(GTE_CMD_MVMVA | GTE_SF | GTE_MX_RT | GTE_V_V1 | GTE_CV_NONE);\n\toutput->values[0][1] = (int16_t) gte_getDataReg(GTE_IR1);\n\toutput->values[1][1] = (int16_t) gte_getDataReg(GTE_IR2);\n\toutput->values[2][1] = (int16_t) gte_getDataReg(GTE_IR3);\n\n\tgte_command(GTE_CMD_MVMVA | GTE_SF | GTE_MX_RT | GTE_V_V2 | GTE_CV_NONE);\n\toutput->values[0][2] = (int16_t) gte_getDataReg(GTE_IR1);\n\toutput->values[1][2] = (int16_t) gte_getDataReg(GTE_IR2);\n\toutput->values[2][2] = (int16_t) gte_getDataReg(GTE_IR3);\n}\n\nstatic void rotateCurrentMatrix(int yaw, int pitch, int roll) {\n\tstatic GTEMatrix multiplied;\n\tint s, c;\n\n\t// For each axis, compute the rotation matrix then \"combine\" it with the\n\t// GTE's current matrix by multiplying the two and writing the result back\n\t// to the GTE's registers.\n\tif (yaw) {\n\t\ts = isin(yaw);\n\t\tc = icos(yaw);\n\n\t\tgte_setColumnVectors(\n\t\t\tc, -s,        0,\n\t\t\ts,  c,        0,\n\t\t\t0,  0, GTE_UNIT\n\t\t);\n\t\tmultiplyCurrentMatrixByVectors(&multiplied);\n\t\tgte_loadRotationMatrix(&multiplied);\n\t}\n\tif (pitch) {\n\t\ts = isin(pitch);\n\t\tc = icos(pitch);\n\n\t\tgte_setColumnVectors(\n\t\t\t c,        0, s,\n\t\t\t 0, GTE_UNIT, 0,\n\t\t\t-s,        0, c\n\t\t);\n\t\tmultiplyCurrentMatrixByVectors(&multiplied);\n\t\tgte_loadRotationMatrix(&multiplied);\n\t}\n\tif (roll) {\n\t\ts = isin(roll);\n\t\tc = icos(roll);\n\n\t\tgte_setColumnVectors(\n\t\t\tGTE_UNIT, 0,  0,\n\t\t\t       0, c, -s,\n\t\t\t       0, s,  c\n\t\t);\n\t\tmultiplyCurrentMatrixByVectors(&multiplied);\n\t\tgte_loadRotationMatrix(&multiplied);\n\t}\n}\n\n// We're going to store the 3D model of our cube as two separate arrays, one\n// containing a list of unique vertices and the other referencing those vertices\n// to build up quadrilateral faces. This approach of having a \"palette\" of\n// vertices, in a similar way to how indexed color works, allows for significant\n// memory savings as most if not all faces usually have vertices in common.\ntypedef struct {\n\tuint8_t  vertices[4];\n\tuint32_t color;\n} Face;\n\n#define NUM_CUBE_VERTICES 8\n#define NUM_CUBE_FACES    6\n\nstatic const GTEVector16 cubeVertices[NUM_CUBE_VERTICES] = {\n\t{ .x = -32, .y = -32, .z = -32 },\n\t{ .x =  32, .y = -32, .z = -32 },\n\t{ .x = -32, .y =  32, .z = -32 },\n\t{ .x =  32, .y =  32, .z = -32 },\n\t{ .x = -32, .y = -32, .z =  32 },\n\t{ .x =  32, .y = -32, .z =  32 },\n\t{ .x = -32, .y =  32, .z =  32 },\n\t{ .x =  32, .y =  32, .z =  32 }\n};\n\n// Note that there are several requirements on the order of vertices:\n// - they must be arranged in a Z-like shape rather than clockwise or\n//   counterclockwise, since the GPU processes a quad with vertices (A, B, C, D)\n//   as two triangles with vertices (A, B, C) and (B, C, D) respectively;\n// - the first 3 vertices must be ordered clockwise when the face is viewed from\n//   the front, as the code relies on this to determine whether or not the quad\n//   is facing the camera (see main()).\n// For instance, only the first of these faces (viewed from the front) has its\n// vertices ordered correctly:\n//     0----1        0----1        2----3\n//     |  / |        | \\/ |        | \\  |\n//     | /  |        | /\\ |        |  \\ |\n//     2----3        3----2        0----1\n//     Correct    Not Z-shaped  Not clockwise\nstatic const Face cubeFaces[NUM_CUBE_FACES] = {\n\t{ .vertices = { 0, 1, 2, 3 }, .color = 0x0000ff },\n\t{ .vertices = { 6, 7, 4, 5 }, .color = 0x00ff00 },\n\t{ .vertices = { 4, 5, 0, 1 }, .color = 0x00ffff },\n\t{ .vertices = { 7, 6, 3, 2 }, .color = 0xff0000 },\n\t{ .vertices = { 6, 4, 2, 0 }, .color = 0xff00ff },\n\t{ .vertices = { 5, 7, 1, 3 }, .color = 0xffff00 }\n};\n\n#define SCREEN_WIDTH  320\n#define SCREEN_HEIGHT 240\n\nint main(int argc, const char **argv) {\n\tinitSerialIO(115200);\n\n\tif ((GPU_GP1 & GP1_STAT_FB_MODE_BITMASK) == GP1_STAT_FB_MODE_PAL) {\n\t\tputs(\"Using PAL mode\");\n\t\tsetupGPU(GP1_MODE_PAL, SCREEN_WIDTH, SCREEN_HEIGHT);\n\t} else {\n\t\tputs(\"Using NTSC mode\");\n\t\tsetupGPU(GP1_MODE_NTSC, SCREEN_WIDTH, SCREEN_HEIGHT);\n\t}\n\n\tsetupGTE(SCREEN_WIDTH, SCREEN_HEIGHT);\n\n\tGPUDMAChain dmaChains[2];\n\tbool        usingSecondFrame = false;\n\tint         frameCounter     = 0;\n\n\tfor (;;) {\n\t\tint bufferX = usingSecondFrame ? SCREEN_WIDTH : 0;\n\t\tint bufferY = 0;\n\n\t\tGPUDMAChain *chain = &dmaChains[usingSecondFrame];\n\t\tusingSecondFrame   = !usingSecondFrame;\n\n\t\tuint32_t *ptr;\n\n\t\tGPU_GP1 = gp1_fbOffset(bufferX, bufferY);\n\n\t\tclearOrderingTable(chain->orderingTable, GPU_ORDERING_TABLE_SIZE);\n\t\tchain->nextPacket = chain->data;\n\n\t\t// Reset the GTE's translation vector (added to each vertex) and\n\t\t// transformation matrix, then modify the matrix to rotate the cube. The\n\t\t// translation vector is used here to move the cube away from the camera\n\t\t// so it can be seen.\n\t\tgte_setControlReg(GTE_TRX,   0);\n\t\tgte_setControlReg(GTE_TRY,   0);\n\t\tgte_setControlReg(GTE_TRZ, 128);\n\t\tgte_setRotationMatrix(\n\t\t\tGTE_UNIT,        0,        0,\n\t\t\t       0, GTE_UNIT,        0,\n\t\t\t       0,        0, GTE_UNIT\n\t\t);\n\n\t\trotateCurrentMatrix(0, frameCounter * 16, frameCounter * 12);\n\t\tframeCounter++;\n\n\t\t// Draw the cube one face at a time.\n\t\tfor (int i = 0; i < NUM_CUBE_FACES; i++) {\n\t\t\tconst Face *face = &cubeFaces[i];\n\n\t\t\t// Apply perspective projection to the first 3 vertices. The GTE can\n\t\t\t// only process up to 3 vertices at a time, so we'll transform the\n\t\t\t// last one separately.\n\t\t\tgte_loadV0(&cubeVertices[face->vertices[0]]);\n\t\t\tgte_loadV1(&cubeVertices[face->vertices[1]]);\n\t\t\tgte_loadV2(&cubeVertices[face->vertices[2]]);\n\t\t\tgte_command(GTE_CMD_RTPT | GTE_SF);\n\n\t\t\t// Determine the winding order of the vertices on screen. If they\n\t\t\t// are ordered clockwise then the face is visible, otherwise it can\n\t\t\t// be culled as it is not facing the camera. Note that\n\t\t\t// gte_getDataReg() always returns a 32-bit unsigned value, but most\n\t\t\t// GTE registers should be interpreted as signed.\n\t\t\tgte_command(GTE_CMD_NCLIP);\n\n\t\t\tif (((int) gte_getDataReg(GTE_MAC0)) <= 0)\n\t\t\t\tcontinue;\n\n\t\t\t// Save the first transformed vertex (the GTE only keeps the X/Y\n\t\t\t// coordinates of the last 3 vertices processed and Z coordinates of\n\t\t\t// the last 4 vertices processed) and apply projection to the last\n\t\t\t// vertex.\n\t\t\tuint32_t xy0 = gte_getDataReg(GTE_SXY0);\n\n\t\t\tgte_loadV0(&cubeVertices[face->vertices[3]]);\n\t\t\tgte_command(GTE_CMD_RTPS | GTE_SF);\n\n\t\t\t// Calculate the average Z coordinate of all vertices and use it to\n\t\t\t// determine the ordering table bucket index for this face.\n\t\t\tgte_command(GTE_CMD_AVSZ4 | GTE_SF);\n\t\t\tint zIndex = (int) gte_getDataReg(GTE_OTZ);\n\n\t\t\tif ((zIndex < 0) || (zIndex >= GPU_ORDERING_TABLE_SIZE))\n\t\t\t\tcontinue;\n\n\t\t\t// Create a new quad and give its vertices the X/Y coordinates\n\t\t\t// calculated by the GTE.\n\t\t\tptr    = allocateGP0Packet(chain, zIndex, 5);\n\t\t\tptr[0] = face->color | gp0_shadedQuad(false, false, false);\n\t\t\tptr[1] = xy0;\n\t\t\tgte_storeDataReg(GTE_SXY0, 2 * 4, ptr);\n\t\t\tgte_storeDataReg(GTE_SXY1, 3 * 4, ptr);\n\t\t\tgte_storeDataReg(GTE_SXY2, 4 * 4, ptr);\n\t\t}\n\n\t\tptr    = allocateGP0Packet(chain, GPU_ORDERING_TABLE_SIZE - 1, 3);\n\t\tptr[0] = gp0_rgb(64, 64, 64) | gp0_vramFill();\n\t\tptr[1] = gp0_xy(bufferX, bufferY);\n\t\tptr[2] = gp0_xy(SCREEN_WIDTH, SCREEN_HEIGHT);\n\n\t\tptr    = allocateGP0Packet(chain, GPU_ORDERING_TABLE_SIZE - 1, 4);\n\t\tptr[0] = gp0_setPage(0, true, false);\n\t\tptr[1] = gp0_fbOffset1(bufferX, bufferY);\n\t\tptr[2] = gp0_fbOffset2(\n\t\t\tbufferX + SCREEN_WIDTH  - 1,\n\t\t\tbufferY + SCREEN_HEIGHT - 1\n\t\t);\n\t\tptr[3] = gp0_fbOrigin(bufferX, bufferY);\n\n\t\twaitForGP0Ready();\n\t\twaitForVSync();\n\t\tsendGPULinkedList(&(chain->orderingTable)[GPU_ORDERING_TABLE_SIZE - 1]);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/08_spinningCube/trig.c",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n/*\n * This is a fast lookup-table-less implementation of fixed-point sine and\n * cosine, based on the isin_S4 implementation from:\n *     https://www.coranac.com/2009/07/sines\n */\n\n#include \"trig.h\"\n\n#define A (1 << 12)\n#define B 19900\n#define\tC  3516\n\nint isin(int x) {\n\tint c = x << (30 - ISIN_SHIFT);\n\tx    -= 1 << ISIN_SHIFT;\n\n\tx <<= 31 - ISIN_SHIFT;\n\tx >>= 31 - ISIN_SHIFT;\n\tx  *= x;\n\tx >>= 2 * ISIN_SHIFT - 14;\n\n\tint y = B - (x * C >> 14);\n\ty     = A - (x * y >> 16);\n\n\treturn (c >= 0) ? y : (-y);\n}\n\nint isin2(int x) {\n\tint c = x << (30 - ISIN2_SHIFT);\n\tx    -= 1 << ISIN2_SHIFT;\n\n\tx <<= 31 - ISIN2_SHIFT;\n\tx >>= 31 - ISIN2_SHIFT;\n\tx  *= x;\n\tx >>= 2 * ISIN2_SHIFT - 14;\n\n\tint y = B - (x * C >> 14);\n\ty     = A - (x * y >> 16);\n\n\treturn (c >= 0) ? y : (-y);\n}\n"
  },
  {
    "path": "src/08_spinningCube/trig.h",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#pragma once\n\n#define ISIN_SHIFT  10\n#define ISIN2_SHIFT 15\n#define ISIN_PI     (1 << (ISIN_SHIFT  + 1))\n#define ISIN2_PI    (1 << (ISIN2_SHIFT + 1))\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nint isin(int x);\nint isin2(int x);\n\nstatic inline int icos(int x) {\n\treturn isin(x + (1 << ISIN_SHIFT));\n}\nstatic inline int icos2(int x) {\n\treturn isin2(x + (1 << ISIN2_SHIFT));\n}\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "src/09_controllers/font.c",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#include <stdint.h>\n#include \"font.h\"\n#include \"gpu.h\"\n#include \"ps1/gpucmd.h\"\n\nstatic const SpriteInfo fontSprites[] = {\n\t{ .x =  6, .y =  0, .width = 2, .height = 9 }, // !\n\t{ .x = 12, .y =  0, .width = 4, .height = 9 }, // \"\n\t{ .x = 18, .y =  0, .width = 6, .height = 9 }, // #\n\t{ .x = 24, .y =  0, .width = 6, .height = 9 }, // $\n\t{ .x = 30, .y =  0, .width = 6, .height = 9 }, // %\n\t{ .x = 36, .y =  0, .width = 6, .height = 9 }, // &\n\t{ .x = 42, .y =  0, .width = 2, .height = 9 }, // '\n\t{ .x = 48, .y =  0, .width = 3, .height = 9 }, // (\n\t{ .x = 54, .y =  0, .width = 3, .height = 9 }, // )\n\t{ .x = 60, .y =  0, .width = 4, .height = 9 }, // *\n\t{ .x = 66, .y =  0, .width = 6, .height = 9 }, // +\n\t{ .x = 72, .y =  0, .width = 3, .height = 9 }, // ,\n\t{ .x = 78, .y =  0, .width = 6, .height = 9 }, // -\n\t{ .x = 84, .y =  0, .width = 2, .height = 9 }, // .\n\t{ .x = 90, .y =  0, .width = 6, .height = 9 }, // /\n\t{ .x =  0, .y =  9, .width = 6, .height = 9 }, // 0\n\t{ .x =  6, .y =  9, .width = 6, .height = 9 }, // 1\n\t{ .x = 12, .y =  9, .width = 6, .height = 9 }, // 2\n\t{ .x = 18, .y =  9, .width = 6, .height = 9 }, // 3\n\t{ .x = 24, .y =  9, .width = 6, .height = 9 }, // 4\n\t{ .x = 30, .y =  9, .width = 6, .height = 9 }, // 5\n\t{ .x = 36, .y =  9, .width = 6, .height = 9 }, // 6\n\t{ .x = 42, .y =  9, .width = 6, .height = 9 }, // 7\n\t{ .x = 48, .y =  9, .width = 6, .height = 9 }, // 8\n\t{ .x = 54, .y =  9, .width = 6, .height = 9 }, // 9\n\t{ .x = 60, .y =  9, .width = 2, .height = 9 }, // :\n\t{ .x = 66, .y =  9, .width = 3, .height = 9 }, // ;\n\t{ .x = 72, .y =  9, .width = 6, .height = 9 }, // <\n\t{ .x = 78, .y =  9, .width = 6, .height = 9 }, // =\n\t{ .x = 84, .y =  9, .width = 6, .height = 9 }, // >\n\t{ .x = 90, .y =  9, .width = 6, .height = 9 }, // ?\n\t{ .x =  0, .y = 18, .width = 6, .height = 9 }, // @\n\t{ .x =  6, .y = 18, .width = 6, .height = 9 }, // A\n\t{ .x = 12, .y = 18, .width = 6, .height = 9 }, // B\n\t{ .x = 18, .y = 18, .width = 6, .height = 9 }, // C\n\t{ .x = 24, .y = 18, .width = 6, .height = 9 }, // D\n\t{ .x = 30, .y = 18, .width = 6, .height = 9 }, // E\n\t{ .x = 36, .y = 18, .width = 6, .height = 9 }, // F\n\t{ .x = 42, .y = 18, .width = 6, .height = 9 }, // G\n\t{ .x = 48, .y = 18, .width = 6, .height = 9 }, // H\n\t{ .x = 54, .y = 18, .width = 4, .height = 9 }, // I\n\t{ .x = 60, .y = 18, .width = 5, .height = 9 }, // J\n\t{ .x = 66, .y = 18, .width = 6, .height = 9 }, // K\n\t{ .x = 72, .y = 18, .width = 6, .height = 9 }, // L\n\t{ .x = 78, .y = 18, .width = 6, .height = 9 }, // M\n\t{ .x = 84, .y = 18, .width = 6, .height = 9 }, // N\n\t{ .x = 90, .y = 18, .width = 6, .height = 9 }, // O\n\t{ .x =  0, .y = 27, .width = 6, .height = 9 }, // P\n\t{ .x =  6, .y = 27, .width = 6, .height = 9 }, // Q\n\t{ .x = 12, .y = 27, .width = 6, .height = 9 }, // R\n\t{ .x = 18, .y = 27, .width = 6, .height = 9 }, // S\n\t{ .x = 24, .y = 27, .width = 6, .height = 9 }, // T\n\t{ .x = 30, .y = 27, .width = 6, .height = 9 }, // U\n\t{ .x = 36, .y = 27, .width = 6, .height = 9 }, // V\n\t{ .x = 42, .y = 27, .width = 6, .height = 9 }, // W\n\t{ .x = 48, .y = 27, .width = 6, .height = 9 }, // X\n\t{ .x = 54, .y = 27, .width = 6, .height = 9 }, // Y\n\t{ .x = 60, .y = 27, .width = 6, .height = 9 }, // Z\n\t{ .x = 66, .y = 27, .width = 3, .height = 9 }, // [\n\t{ .x = 72, .y = 27, .width = 6, .height = 9 }, // Backslash\n\t{ .x = 78, .y = 27, .width = 3, .height = 9 }, // ]\n\t{ .x = 84, .y = 27, .width = 4, .height = 9 }, // ^\n\t{ .x = 90, .y = 27, .width = 6, .height = 9 }, // _\n\t{ .x =  0, .y = 36, .width = 3, .height = 9 }, // `\n\t{ .x =  6, .y = 36, .width = 6, .height = 9 }, // a\n\t{ .x = 12, .y = 36, .width = 6, .height = 9 }, // b\n\t{ .x = 18, .y = 36, .width = 6, .height = 9 }, // c\n\t{ .x = 24, .y = 36, .width = 6, .height = 9 }, // d\n\t{ .x = 30, .y = 36, .width = 6, .height = 9 }, // e\n\t{ .x = 36, .y = 36, .width = 5, .height = 9 }, // f\n\t{ .x = 42, .y = 36, .width = 6, .height = 9 }, // g\n\t{ .x = 48, .y = 36, .width = 5, .height = 9 }, // h\n\t{ .x = 54, .y = 36, .width = 2, .height = 9 }, // i\n\t{ .x = 60, .y = 36, .width = 4, .height = 9 }, // j\n\t{ .x = 66, .y = 36, .width = 5, .height = 9 }, // k\n\t{ .x = 72, .y = 36, .width = 2, .height = 9 }, // l\n\t{ .x = 78, .y = 36, .width = 6, .height = 9 }, // m\n\t{ .x = 84, .y = 36, .width = 5, .height = 9 }, // n\n\t{ .x = 90, .y = 36, .width = 6, .height = 9 }, // o\n\t{ .x =  0, .y = 45, .width = 6, .height = 9 }, // p\n\t{ .x =  6, .y = 45, .width = 6, .height = 9 }, // q\n\t{ .x = 12, .y = 45, .width = 6, .height = 9 }, // r\n\t{ .x = 18, .y = 45, .width = 6, .height = 9 }, // s\n\t{ .x = 24, .y = 45, .width = 5, .height = 9 }, // t\n\t{ .x = 30, .y = 45, .width = 5, .height = 9 }, // u\n\t{ .x = 36, .y = 45, .width = 6, .height = 9 }, // v\n\t{ .x = 42, .y = 45, .width = 6, .height = 9 }, // w\n\t{ .x = 48, .y = 45, .width = 6, .height = 9 }, // x\n\t{ .x = 54, .y = 45, .width = 6, .height = 9 }, // y\n\t{ .x = 60, .y = 45, .width = 5, .height = 9 }, // z\n\t{ .x = 66, .y = 45, .width = 4, .height = 9 }, // {\n\t{ .x = 72, .y = 45, .width = 2, .height = 9 }, // |\n\t{ .x = 78, .y = 45, .width = 4, .height = 9 }, // }\n\t{ .x = 84, .y = 45, .width = 6, .height = 9 }, // ~\n\t{ .x = 90, .y = 45, .width = 6, .height = 9 }  // Invalid character\n};\n\nvoid printString(\n\tGPUDMAChain       *chain,\n\tconst TextureInfo *font,\n\tint               x,\n\tint               y,\n\tconst char        *str\n) {\n\tint currentX = x, currentY = y;\n\n\tuint32_t *ptr;\n\n\t// Start by sending a texture page command to tell the GPU to use the font's\n\t// spritesheet. The page setting persists when drawing rectangles, so\n\t// sending it here just once is enough.\n\tptr    = allocateGP0Packet(chain, 1);\n\tptr[0] = gp0_setPage(font->page, false, false);\n\n\t// Iterate over every character in the string.\n\tfor (; *str; str++) {\n\t\tchar ch = *str;\n\n\t\t// Check if the character is \"special\" and shall be handled without\n\t\t// drawing any sprite, or if it's invalid and should be rendered as a\n\t\t// box with a question mark (character code 127).\n\t\tswitch (ch) {\n\t\t\tcase '\\t':\n\t\t\t\tcurrentX += FONT_TAB_WIDTH - 1;\n\t\t\t\tcurrentX -= currentX % FONT_TAB_WIDTH;\n\t\t\t\tcontinue;\n\n\t\t\tcase '\\n':\n\t\t\t\tcurrentX  = x;\n\t\t\t\tcurrentY += FONT_LINE_HEIGHT;\n\t\t\t\tcontinue;\n\n\t\t\tcase ' ':\n\t\t\t\tcurrentX += FONT_SPACE_WIDTH;\n\t\t\t\tcontinue;\n\n\t\t\tcase '\\x80' ... '\\xff':\n\t\t\t\tch = '\\x7f';\n\t\t\t\tbreak;\n\t\t}\n\n\t\t// If the character was not a tab, newline or space, fetch its\n\t\t// respective entry from the sprite coordinate table.\n\t\tconst SpriteInfo *sprite = &fontSprites[ch - FONT_FIRST_TABLE_CHAR];\n\n\t\t// Draw the character, summing the UV coordinates of the spritesheet in\n\t\t// VRAM to those of the sprite itself within the sheet. Enable blending\n\t\t// to make sure any semitransparent pixels in the font get rendered\n\t\t// correctly.\n\t\tptr    = allocateGP0Packet(chain, 4);\n\t\tptr[0] = gp0_rectangle(true, true, true);\n\t\tptr[1] = gp0_xy(currentX, currentY);\n\t\tptr[2] = gp0_uv(font->u + sprite->x, font->v + sprite->y, font->clut);\n\t\tptr[3] = gp0_xy(sprite->width, sprite->height);\n\n\t\t// Move onto the next character.\n\t\tcurrentX += sprite->width;\n\t}\n}\n"
  },
  {
    "path": "src/09_controllers/font.h",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#pragma once\n\n#include <stdint.h>\n#include \"gpu.h\"\n\n#define FONT_FIRST_TABLE_CHAR '!'\n#define FONT_SPACE_WIDTH       4\n#define FONT_TAB_WIDTH        32\n#define FONT_LINE_HEIGHT      10\n\ntypedef struct {\n\tuint8_t x, y, width, height;\n} SpriteInfo;\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid printString(\n\tGPUDMAChain       *chain,\n\tconst TextureInfo *font,\n\tint               x,\n\tint               y,\n\tconst char        *str\n);\n\n#ifdef __cplusplus\n}\n#endif\n\n"
  },
  {
    "path": "src/09_controllers/gpu.c",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#include <assert.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include \"gpu.h\"\n#include \"ps1/gpucmd.h\"\n#include \"ps1/registers.h\"\n\n#define DMA_MAX_CHUNK_SIZE 16\n\nvoid setupGPU(GP1VideoMode mode, int width, int height) {\n\tint x = 0x760;\n\tint y = (mode == GP1_MODE_PAL) ? 0xa3 : 0x88;\n\n\tGP1HorizontalRes horizontalRes = GP1_HRES_320;\n\tGP1VerticalRes   verticalRes   = GP1_VRES_256;\n\n\tint offsetX = (width  * gp1_clockMultiplierH(horizontalRes)) / 2;\n\tint offsetY = (height / gp1_clockDividerV(verticalRes))      / 2;\n\n\tGPU_GP1 = gp1_resetGPU();\n\tGPU_GP1 = gp1_fbRangeH(x - offsetX, x + offsetX);\n\tGPU_GP1 = gp1_fbRangeV(y - offsetY, y + offsetY);\n\tGPU_GP1 = gp1_fbMode(\n\t\thorizontalRes,\n\t\tverticalRes,\n\t\tmode,\n\t\tfalse,\n\t\tGP1_COLOR_16BPP\n\t);\n\tGPU_GP1 = gp1_dispBlank(false);\n\n\tDMA_DPCR         |= DMA_DPCR_CH_ENABLE(DMA_GPU);\n\tDMA_CHCR(DMA_GPU) = 0;\n\n\tGPU_GP1 = gp1_dmaRequestMode(GP1_DREQ_GP0_WRITE);\n}\n\nvoid waitForGP0Ready(void) {\n\twhile (!(GPU_GP1 & GP1_STAT_CMD_READY))\n\t\t__asm__ volatile(\"\");\n}\n\nvoid waitForGPUDMADone(void) {\n\twhile (DMA_CHCR(DMA_GPU) & DMA_CHCR_ENABLE)\n\t\t__asm__ volatile(\"\");\n}\n\nvoid waitForVSync(void) {\n\twhile (!(IRQ_STAT & (1 << IRQ_VSYNC)))\n\t\t__asm__ volatile(\"\");\n\n\tIRQ_STAT = ~(1 << IRQ_VSYNC);\n}\n\nvoid sendGPULinkedList(const void *data) {\n\twaitForGPUDMADone();\n\tassert(!((uint32_t) data % 4));\n\n\tDMA_MADR(DMA_GPU) = (uint32_t) data;\n\tDMA_CHCR(DMA_GPU) = 0\n\t\t| DMA_CHCR_WRITE\n\t\t| DMA_CHCR_MODE_LIST\n\t\t| DMA_CHCR_ENABLE;\n}\n\nvoid sendVRAMData(\n\tconst void *data,\n\tint        x,\n\tint        y,\n\tint        width,\n\tint        height\n) {\n\twaitForGPUDMADone();\n\tassert(!((uint32_t) data % 4));\n\n\tsize_t length = (width * height + 1) / 2;\n\tsize_t chunkSize, numChunks;\n\n\tif (length < DMA_MAX_CHUNK_SIZE) {\n\t\tchunkSize = length;\n\t\tnumChunks = 1;\n\t} else {\n\t\tchunkSize = DMA_MAX_CHUNK_SIZE;\n\t\tnumChunks = length / DMA_MAX_CHUNK_SIZE;\n\n\t\tassert(!(length % DMA_MAX_CHUNK_SIZE));\n\t}\n\n\twaitForGP0Ready();\n\tGPU_GP0 = gp0_vramWrite();\n\tGPU_GP0 = gp0_xy(x, y);\n\tGPU_GP0 = gp0_xy(width, height);\n\n\tDMA_MADR(DMA_GPU) = (uint32_t) data;\n\tDMA_BCR (DMA_GPU) = chunkSize | (numChunks << 16);\n\tDMA_CHCR(DMA_GPU) = 0\n\t\t| DMA_CHCR_WRITE\n\t\t| DMA_CHCR_MODE_SLICE\n\t\t| DMA_CHCR_ENABLE;\n}\n\nuint32_t *allocateGP0Packet(GPUDMAChain *chain, int numCommands) {\n\tassert((numCommands >= 0) && (numCommands <= DMA_MAX_CHUNK_SIZE));\n\n\tuint32_t *ptr      = chain->nextPacket;\n\tchain->nextPacket += numCommands + 1;\n\n\t*ptr = gp0_tag(numCommands, chain->nextPacket);\n\tassert(chain->nextPacket < &(chain->data)[GPU_CHAIN_BUFFER_SIZE]);\n\n\treturn &ptr[1];\n}\n\nvoid uploadTexture(\n\tTextureInfo *info,\n\tconst void  *data,\n\tint         x,\n\tint         y,\n\tint         width,\n\tint         height\n) {\n\tassert((width <= 256) && (height <= 256));\n\n\tsendVRAMData(data, x, y, width, height);\n\twaitForGPUDMADone();\n\tGPU_GP0 = gp0_flushCache();\n\n\tinfo->page   = gp0_page(\n\t\tx /  64,\n\t\ty / 256,\n\t\tGP0_BLEND_SEMITRANS,\n\t\tGP0_COLOR_16BPP\n\t);\n\tinfo->clut   = 0;\n\tinfo->u      = (uint8_t)  (x %  64);\n\tinfo->v      = (uint8_t)  (y % 256);\n\tinfo->width  = (uint16_t) width;\n\tinfo->height = (uint16_t) height;\n}\n\nvoid uploadIndexedTexture(\n\tTextureInfo   *info,\n\tconst void    *image,\n\tconst void    *palette,\n\tint           imageX,\n\tint           imageY,\n\tint           paletteX,\n\tint           paletteY,\n\tint           width,\n\tint           height,\n\tGP0ColorDepth colorDepth\n) {\n\tassert((width <= 256) && (height <= 256));\n\n\tint numColors    = (colorDepth == GP0_COLOR_8BPP) ? 256 : 16;\n\tint widthDivider = (colorDepth == GP0_COLOR_8BPP) ?   2 :  4;\n\n\tassert(!(paletteX % 16) && ((paletteX + numColors) <= 1024));\n\n\tsendVRAMData(image, imageX, imageY, width / widthDivider, height);\n\twaitForGPUDMADone();\n\tsendVRAMData(palette, paletteX, paletteY, numColors, 1);\n\twaitForGPUDMADone();\n\tGPU_GP0 = gp0_flushCache();\n\n\tinfo->page   = gp0_page(\n\t\timageX /  64,\n\t\timageY / 256,\n\t\tGP0_BLEND_SEMITRANS,\n\t\tcolorDepth\n\t);\n\tinfo->clut   = gp0_clut(paletteX / 16, paletteY);\n\tinfo->u      = (uint8_t)  ((imageX %  64) * widthDivider);\n\tinfo->v      = (uint8_t)   (imageY % 256);\n\tinfo->width  = (uint16_t) width;\n\tinfo->height = (uint16_t) height;\n}\n"
  },
  {
    "path": "src/09_controllers/gpu.h",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#pragma once\n\n#include <stdint.h>\n#include \"ps1/gpucmd.h\"\n\n#define GPU_CHAIN_BUFFER_SIZE 1024\n\ntypedef struct {\n\tuint32_t data[GPU_CHAIN_BUFFER_SIZE];\n\tuint32_t *nextPacket;\n} GPUDMAChain;\n\ntypedef struct {\n\tuint8_t  u, v;\n\tuint16_t width, height;\n\tuint16_t page, clut;\n} TextureInfo;\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid setupGPU(GP1VideoMode mode, int width, int height);\nvoid waitForGP0Ready(void);\nvoid waitForGPUDMADone(void);\nvoid waitForVSync(void);\n\nvoid sendGPULinkedList(const void *data);\nvoid sendVRAMData(\n\tconst void *data,\n\tint        x,\n\tint        y,\n\tint        width,\n\tint        height\n);\nuint32_t *allocateGP0Packet(GPUDMAChain *chain, int numCommands);\n\nvoid uploadTexture(\n\tTextureInfo *info,\n\tconst void  *data,\n\tint         x,\n\tint         y,\n\tint         width,\n\tint         height\n);\nvoid uploadIndexedTexture(\n\tTextureInfo   *info,\n\tconst void    *image,\n\tconst void    *palette,\n\tint           imageX,\n\tint           imageY,\n\tint           paletteX,\n\tint           paletteY,\n\tint           width,\n\tint           height,\n\tGP0ColorDepth colorDepth\n);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "src/09_controllers/main.c",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n/*\n * This example implements a simple controller tester, showing how to send\n * commands to and obtain data from connected controllers.\n *\n * Compared to other consoles where the state of all inputs is sometimes easily\n * accessible through registers, the PS1 has its controllers and memory cards\n * interfaced via a serial bus (similar to SPI). Both communicate using a simple\n * packet-based protocol, listening for request packets sent by the console and\n * replying with appropriate responses. Each packet consists of an address, a\n * command and a series of parameters, while responses will typically contain\n * information about the controller and the current state of its buttons in\n * addition to any data returned by the command.\n *\n * Communication is done over the SIO0 serial interface, similar to the SIO1\n * interface we used in the hello world example. All ports share the same bus,\n * so an addressing system is used to specify which device shall respond to each\n * packet. Understanding this example may require some basic familiarity with\n * serial ports and their usage, although I tried to add as many comments as I\n * could to explain what is going on under the hood.\n */\n\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdint.h>\n#include <stdio.h>\n#include \"font.h\"\n#include \"gpu.h\"\n#include \"ps1/gpucmd.h\"\n#include \"ps1/registers.h\"\n\nstatic void delayMicroseconds(int time) {\n\t// Calculate the approximate number of CPU cycles that need to be burned,\n\t// assuming a 33.8688 MHz clock (1 us = 33.8688 = ~33.875 = 271 / 8 cycles).\n\t// The loop consists of a branch and a decrement, thus each iteration will\n\t// burn 2 cycles.\n\ttime = ((time * 271) + 4) / 8;\n\n\t__asm__ volatile(\n\t\t// The .set noreorder directive will prevent the assembler from trying\n\t\t// to \"hide\" the branch instruction's delay slot by shuffling nearby\n\t\t// instructions. .set push and .set pop are used to save and restore the\n\t\t// assembler's settings respectively, ensuring the noreorder flag will\n\t\t// not affect any other code.\n\t\t\".set push\\n\"\n\t\t\".set noreorder\\n\"\n\t\t\"bgtz  %0, .\\n\"\n\t\t\"addiu %0, -2\\n\"\n\t\t\".set pop\\n\"\n\t\t: \"+r\"(time)\n\t);\n}\n\nstatic void initControllerBus(void) {\n\t// Reset the serial interface, initialize it with the settings used by\n\t// controllers and memory cards (250000bps, 8 data bits) and configure it to\n\t// send a signal to the interrupt controller whenever the DSR input is\n\t// pulsed (see below).\n\tSIO_CTRL(0) = SIO_CTRL_RESET;\n\n\tSIO_MODE(0) = 0\n\t\t| SIO_MODE_BAUD_DIV1\n\t\t| SIO_MODE_DATA_8;\n\tSIO_BAUD(0) = F_CPU / 250000;\n\tSIO_CTRL(0) = 0\n\t\t| SIO_CTRL_TX_ENABLE\n\t\t| SIO_CTRL_RX_ENABLE\n\t\t| SIO_CTRL_DSR_IRQ_ENABLE;\n}\n\nstatic bool waitForAcknowledge(int timeout) {\n\t// Controllers and memory cards will acknowledge bytes received by sending\n\t// short pulses over the DSR line, which will be forwarded by the serial\n\t// interface to the interrupt controller. This is not guaranteed to happen\n\t// (it will not if e.g. no device is connected), so we have to implement a\n\t// timeout to avoid waiting forever in such cases.\n\tfor (; timeout > 0; timeout -= 10) {\n\t\tif (IRQ_STAT & (1 << IRQ_SIO0)) {\n\t\t\t// Reset the interrupt controller and serial interface's flags to\n\t\t\t// ensure the interrupt can be triggered again.\n\t\t\tIRQ_STAT     = ~(1 << IRQ_SIO0);\n\t\t\tSIO_CTRL(0) |= SIO_CTRL_ACKNOWLEDGE;\n\n\t\t\treturn true;\n\t\t}\n\n\t\tdelayMicroseconds(10);\n\t}\n\n\treturn false;\n}\n\n// As the controller bus is shared with memory cards, an addressing mechanism is\n// used to ensure packets are processed by a single device at a time. The first\n// byte of each request packet is thus the \"address\" of the peripheral that\n// shall respond to it.\ntypedef enum {\n\tSIO0_ADDR_CONTROLLER  = 0x01,\n\tSIO0_ADDR_MEMORY_CARD = 0x81\n} SIO0DeviceAddress;\n\n// The address is followed by a command byte and any required parameters. The\n// only command used in this example (and supported by all controllers) is\n// SIO0_PAD_POLL, however some controllers additionally support a \"configuration\n// mode\" which grants access to an extended command set.\ntypedef enum {\n\t// Controller commands\n\tSIO0_PAD_POLL        = 'B', // Read controller state\n\tSIO0_PAD_CONFIG_MODE = 'C', // Enter or exit configuration mode\n\n\t// Configuration mode commands\n\tSIO0_CFG_INIT_PRESSURE  = '@', // Initialize DualShock 2 pressure sensors\n\tSIO0_CFG_SET_ANALOG     = 'D', // Set analog mode/LED state\n\tSIO0_CFG_GET_ANALOG     = 'E', // Get analog mode/LED state\n\tSIO0_CFG_GET_ACT_INFO   = 'F', // Get information about a feedback actuator\n\tSIO0_CFG_GET_ACT_LIST   = 'G', // Get list of all feedback actuators\n\tSIO0_CFG_GET_ACT_STATE  = 'H', // Get current state of feedback actuators\n\tSIO0_CFG_GET_MODE       = 'L', // Get list of all supported modes\n\tSIO0_CFG_REQUEST_SETUP  = 'M', // Configure poll request format\n\tSIO0_CFG_RESPONSE_SETUP = 'O', // Configure poll response format\n\n\t// Memory card commands\n\tSIO0_CARD_READ       = 'R', // Read 128-byte sector\n\tSIO0_CARD_GET_SIZE   = 'S', // Retrieve size information\n\tSIO0_CARD_WRITE      = 'W'  // Write 128-byte sector\n} SIO0_DeviceCommand;\n\n#define DTR_DELAY    60\n#define DSR_TIMEOUT 120\n\nstatic void selectControllerPort(int port) {\n\t// Set or clear the bit that controls which set of controller and memory\n\t// card ports is going to have its DTR (port select) signal asserted. The\n\t// actual serial bus is shared between all ports, however devices will not\n\t// process packets if DTR is not asserted on the port they are plugged into.\n\tif (port)\n\t\tSIO_CTRL(0) |= SIO_CTRL_CS_PORT_2;\n\telse\n\t\tSIO_CTRL(0) &= ~SIO_CTRL_CS_PORT_2;\n}\n\nstatic uint8_t exchangeByte(uint8_t value) {\n\t// Wait until the interface is ready to accept a byte to send, then wait for\n\t// it to finish receiving the byte sent by the device.\n\twhile (!(SIO_STAT(0) & SIO_STAT_TX_NOT_FULL))\n\t\t__asm__ volatile(\"\");\n\n\tSIO_DATA(0) = value;\n\n\twhile (!(SIO_STAT(0) & SIO_STAT_RX_NOT_EMPTY))\n\t\t__asm__ volatile(\"\");\n\n\treturn SIO_DATA(0);\n}\n\nstatic size_t exchangeSIO0Packet(\n\tSIO0DeviceAddress address,\n\tconst uint8_t     *request,\n\tuint8_t           *response,\n\tsize_t            reqLength,\n\tsize_t            maxRespLength\n) {\n\t// Reset the interrupt flag and assert the DTR signal to tell the controller\n\t// or memory card that we're about to send a packet. Devices may take some\n\t// time to prepare for incoming bytes so we need a small delay here.\n\tIRQ_STAT     = ~(1 << IRQ_SIO0);\n\tSIO_CTRL(0) |= SIO_CTRL_DTR | SIO_CTRL_ACKNOWLEDGE;\n\tdelayMicroseconds(DTR_DELAY);\n\n\tsize_t respLength = 0;\n\n\t// Send the address byte and wait for the device to respond with a pulse on\n\t// the DSR line. If no response is received assume no device is connected,\n\t// otherwise make sure the serial interface's data buffer is empty to\n\t// prepare for the actual packet transfer.\n\tSIO_DATA(0) = address;\n\n\tif (waitForAcknowledge(DSR_TIMEOUT)) {\n\t\twhile (SIO_STAT(0) & SIO_STAT_RX_NOT_EMPTY)\n\t\t\tSIO_DATA(0);\n\n\t\t// Send and receive the packet simultaneously one byte at a time,\n\t\t// padding it with zeroes if the packet we are receiving is longer than\n\t\t// the data being sent.\n\t\twhile (respLength < maxRespLength) {\n\t\t\tif (reqLength > 0) {\n\t\t\t\t*(response++) = exchangeByte(*(request++));\n\t\t\t\treqLength--;\n\t\t\t} else {\n\t\t\t\t*(response++) = exchangeByte(0);\n\t\t\t}\n\n\t\t\trespLength++;\n\n\t\t\t// The device will keep sending DSR pulses as long as there is more\n\t\t\t// data to transfer. If no more pulses are received, terminate the\n\t\t\t// transfer.\n\t\t\tif (!waitForAcknowledge(DSR_TIMEOUT))\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Release DSR, allowing the device to go idle.\n\tdelayMicroseconds(DTR_DELAY);\n\tSIO_CTRL(0) &= ~SIO_CTRL_DTR;\n\n\treturn respLength;\n}\n\n// All packets sent by controllers in response to a poll command include a 4-bit\n// device type identifier as well as a bitfield describing the state of up to 16\n// buttons.\nstatic const char *const controllerTypes[] = {\n\t\"Unknown\",            // ID 0x0\n\t\"Mouse\",              // ID 0x1\n\t\"neGcon\",             // ID 0x2\n\t\"Konami Justifier\",   // ID 0x3\n\t\"Digital controller\", // ID 0x4\n\t\"Analog stick\",       // ID 0x5\n\t\"Guncon\",             // ID 0x6\n\t\"Analog controller\",  // ID 0x7\n\t\"Multitap\",           // ID 0x8\n\t\"Keyboard\",           // ID 0x9\n\t\"Unknown\",            // ID 0xa\n\t\"Unknown\",            // ID 0xb\n\t\"Unknown\",            // ID 0xc\n\t\"Unknown\",            // ID 0xd\n\t\"Jogcon\",             // ID 0xe\n\t\"Configuration mode\"  // ID 0xf\n};\n\nstatic const char *const buttonNames[] = {\n\t\"Select\",   // Bit  0\n\t\"L3\",       // Bit  1\n\t\"R3\",       // Bit  2\n\t\"Start\",    // Bit  3\n\t\"Up\",       // Bit  4\n\t\"Right\",    // Bit  5\n\t\"Down\",     // Bit  6\n\t\"Left\",     // Bit  7\n\t\"L2\",       // Bit  8\n\t\"R2\",       // Bit  9\n\t\"L1\",       // Bit 10\n\t\"R1\",       // Bit 11\n\t\"Triangle\", // Bit 12\n\t\"Circle\",   // Bit 13\n\t\"X\",        // Bit 14\n\t\"Square\"    // Bit 15\n};\n\nstatic void printControllerInfo(int port, char *output) {\n\t// Build the request packet.\n\tuint8_t request[4], response[8];\n\tchar    *ptr = output;\n\n\trequest[0] = SIO0_PAD_POLL; // Command\n\trequest[1] = 0x00;          // Multitap address\n\trequest[2] = 0x00;          // Actuator control 1\n\trequest[3] = 0x00;          // Actuator control 2\n\n\t// Send the request to the specified controller port and grab the response.\n\t// Note that this is a relatively slow process and should be done only once\n\t// per frame, unless higher polling rates are desired.\n\tselectControllerPort(port);\n\tsize_t respLength = exchangeSIO0Packet(\n\t\tSIO0_ADDR_CONTROLLER,\n\t\trequest,\n\t\tresponse,\n\t\tsizeof(request),\n\t\tsizeof(response)\n\t);\n\n\tptr += sprintf(ptr, \"Port %d:\\n\", port + 1);\n\n\tif (respLength < 4) {\n\t\t// All controllers reply with at least 4 bytes of data.\n\t\tptr += sprintf(ptr, \"  No controller connected\");\n\t\treturn;\n\t}\n\n\t// The first byte of the response contains the device type ID in the upper\n\t// nibble, as well as the length of the packet's payload in 2-byte units in\n\t// the lower nibble.\n\tptr += sprintf(\n\t\tptr,\n\t\t\"  Controller type:\\t%s\\n\"\n\t\t\"  Buttons pressed:\\t\",\n\t\tcontrollerTypes[response[0] >> 4]\n\t);\n\n\t// Bytes 2 and 3 hold a bitfield representing the state all buttons. As each\n\t// bit is active low (i.e. a zero represents a button being pressed), the\n\t// entire field must be inverted.\n\tuint16_t buttons = (response[2] | (response[3] << 8)) ^ 0xffff;\n\n\tfor (int i = 0; i < 16; i++) {\n\t\tif ((buttons >> i) & 1)\n\t\t\tptr += sprintf(ptr, \"%s \", buttonNames[i]);\n\t}\n\n\tptr += sprintf(ptr, \"\\n  Response data:\\t\");\n\n\tfor (size_t i = 0; i < respLength; i++)\n\t\tptr += sprintf(ptr, \"%02X \", response[i]);\n}\n\n#define SCREEN_WIDTH     320\n#define SCREEN_HEIGHT    240\n#define FONT_WIDTH        96\n#define FONT_HEIGHT       56\n#define FONT_COLOR_DEPTH GP0_COLOR_4BPP\n\nextern const uint8_t fontTexture[], fontPalette[];\n\nint main(int argc, const char **argv) {\n\tinitSerialIO(115200);\n\tinitControllerBus();\n\n\tif ((GPU_GP1 & GP1_STAT_FB_MODE_BITMASK) == GP1_STAT_FB_MODE_PAL) {\n\t\tputs(\"Using PAL mode\");\n\t\tsetupGPU(GP1_MODE_PAL, SCREEN_WIDTH, SCREEN_HEIGHT);\n\t} else {\n\t\tputs(\"Using NTSC mode\");\n\t\tsetupGPU(GP1_MODE_NTSC, SCREEN_WIDTH, SCREEN_HEIGHT);\n\t}\n\n\tTextureInfo font;\n\n\tuploadIndexedTexture(\n\t\t&font,\n\t\tfontTexture,\n\t\tfontPalette,\n\t\tSCREEN_WIDTH * 2,\n\t\t0,\n\t\tSCREEN_WIDTH * 2,\n\t\tFONT_HEIGHT,\n\t\tFONT_WIDTH,\n\t\tFONT_HEIGHT,\n\t\tFONT_COLOR_DEPTH\n\t);\n\n\tGPUDMAChain dmaChains[2];\n\tbool        usingSecondFrame = false;\n\n\tfor (;;) {\n\t\tint bufferX = usingSecondFrame ? SCREEN_WIDTH : 0;\n\t\tint bufferY = 0;\n\n\t\tGPUDMAChain *chain = &dmaChains[usingSecondFrame];\n\t\tusingSecondFrame   = !usingSecondFrame;\n\n\t\tuint32_t *ptr;\n\n\t\tGPU_GP1 = gp1_fbOffset(bufferX, bufferY);\n\n\t\tchain->nextPacket = chain->data;\n\n\t\tptr    = allocateGP0Packet(chain, 4);\n\t\tptr[0] = gp0_setPage(0, true, false);\n\t\tptr[1] = gp0_fbOffset1(bufferX, bufferY);\n\t\tptr[2] = gp0_fbOffset2(\n\t\t\tbufferX + SCREEN_WIDTH  - 1,\n\t\t\tbufferY + SCREEN_HEIGHT - 1\n\t\t);\n\t\tptr[3] = gp0_fbOrigin(bufferX, bufferY);\n\n\t\tptr    = allocateGP0Packet(chain, 3);\n\t\tptr[0] = gp0_rgb(64, 64, 64) | gp0_vramFill();\n\t\tptr[1] = gp0_xy(bufferX, bufferY);\n\t\tptr[2] = gp0_xy(SCREEN_WIDTH, SCREEN_HEIGHT);\n\n\t\t// Poll both controller ports once per frame. Memory cards are ignored\n\t\t// in this example.\n\t\tfor (int i = 0; i < 2; i++) {\n\t\t\tint  offset = i * 64;\n\t\t\tchar buffer[256];\n\n\t\t\tprintControllerInfo(i, buffer);\n\t\t\tprintString(chain, &font, 16, 32 + offset, buffer);\n\t\t}\n\n\t\t*(chain->nextPacket) = gp0_endTag(0);\n\n\t\twaitForGP0Ready();\n\t\twaitForVSync();\n\t\tsendGPULinkedList(chain->data);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/libc/assert.h",
    "content": "/*\n * ps1-bare-metal - (C) 2023 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#pragma once\n\n// NDEBUG is automatically defined by CMake when the executable is built in\n// release mode.\n#ifdef NDEBUG\n#define assert(expr) ((void) (expr))\n#else\n#define assert(expr) \\\n\t((expr) ? ((void) 0) : _assertAbort(__FILE__, __LINE__, #expr))\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid _assertAbort(const char *file, int line, const char *expr);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "src/libc/clz.s",
    "content": "# ps1-bare-metal - (C) 2023-2025 spicyjpeg\n#\n# Permission to use, copy, modify, and/or distribute this software for any\n# purpose with or without fee is hereby granted, provided that the above\n# copyright notice and this permission notice appear in all copies.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n# PERFORMANCE OF THIS SOFTWARE.\n\n.set noreorder\n\n# libgcc provides two functions used internally by GCC to count the number of\n# leading zeroes in a value, __clzsi2() (32-bit) and __clzdi2() (64-bit). We're\n# going to override them with smaller implementations that make use of the GTE's\n# LZCS/LZCR registers.\n\n.set GTE_LZCS, $30 # Leading zero count input\n.set GTE_LZCR, $31 # Leading zero count output\n\n.section .text.__clzsi2, \"ax\", @progbits\n.global __clzsi2\n.type __clzsi2, @function\n\n__clzsi2:\n\t# if (value & (1 << 31))\n\t#     return 0;\n\tmtc2  $a0, GTE_LZCS\n\tbltz  $a0, .Lreturn\n\tli    $v0, 0\n\n\t# else\n\t#     return countLeadingZeroes(value);\n\tmfc2  $v0, GTE_LZCR\n\n.Lreturn:\n\tjr    $ra\n\tnop\n\n.section .text.__clzdi2, \"ax\", @progbits\n.global __clzdi2\n.type __clzdi2, @function\n\n__clzdi2:\n\t# if (msb & (1 << 31))\n\t#     return 0;\n\tmtc2  $a1, GTE_LZCS\n\tbltz  $a1, .Lreturn2\n\tli    $v0, 0\n\n\t# else if (msb)\n\t#     return countLeadingZeroes(msb);\n\tbnez  $a1, .LreturnMSB\n\tnop\n\n.LnoMSB:\n\t# else if (lsb & (1 << 31))\n\t#     return 32;\n\tmtc2  $a0, GTE_LZCS\n\tbltz  $a0, .Lreturn2\n\tli    $v0, 32\n\n\t# else\n\t#     return 32 + countLeadingZeroes(lsb);\n\tmfc2  $v0, GTE_LZCR\n\n\tjr    $ra\n\taddiu $v0, 32\n\n.LreturnMSB:\n\tmfc2  $v0, GTE_LZCR\n\n.Lreturn2:\n\tjr    $ra\n\tnop\n"
  },
  {
    "path": "src/libc/crt0.c",
    "content": "/*\n * ps1-bare-metal - (C) 2023 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#include <stddef.h>\n#include <stdint.h>\n\ntypedef void (*Function)(void);\n\n/* Linker symbols */\n\n// These are defined by the linker script. Note that these are not variables,\n// they are virtual symbols whose location matches their value. The simplest way\n// to turn them into pointers is to declare them as arrays.\nextern char _bssStart[], _bssEnd[];\n\nextern const Function _preinitArrayStart[], _preinitArrayEnd[];\nextern const Function _initArrayStart[],    _initArrayEnd[];\nextern const Function _finiArrayStart[],    _finiArrayEnd[];\n\n/* Heap API (used by malloc) */\n\n#define ALIGN(x, n) (((x) + ((n) - 1)) & ~((n) - 1))\n\nstatic uintptr_t _heapEnd   = (uintptr_t) _bssEnd;\nstatic uintptr_t _heapLimit = 0x80200000; // TODO: add a way to change this\n\nvoid *sbrk(ptrdiff_t incr) {\n\tuintptr_t currentEnd = _heapEnd;\n\tuintptr_t newEnd     = ALIGN(currentEnd + incr, 8);\n\n\tif (newEnd >= _heapLimit)\n\t\treturn 0;\n\n\t_heapEnd = newEnd;\n\treturn (void *) currentEnd;\n}\n\n/* Program entry point */\n\nint main(int argc, const char **argv);\n\nint _start(int argc, const char **argv) {\n\t// Set $gp to point to the middle of the .sdata/.sbss sections, ensuring\n\t// variables placed in those sections can be quickly accessed. See the\n\t// linker script for more details.\n\t__asm__ volatile(\"la $gp, _gp\\n\");\n\n\t// Set all uninitialized variables to zero by clearing the BSS section.\n\t__builtin_memset(_bssStart, 0, _bssEnd - _bssStart);\n\n\t// Invoke all global constructors if any, then main() and finally all global\n\t// destructors.\n\tfor (const Function *ctor = _preinitArrayStart; ctor < _preinitArrayEnd; ctor++)\n\t\t(*ctor)();\n\tfor (const Function *ctor = _initArrayStart; ctor < _initArrayEnd; ctor++)\n\t\t(*ctor)();\n\n\tint returnValue = main(argc, argv);\n\n\tfor (const Function *dtor = _finiArrayStart; dtor < _finiArrayEnd; dtor++)\n\t\t(*dtor)();\n\n\treturn returnValue;\n}\n"
  },
  {
    "path": "src/libc/ctype.h",
    "content": "/*\n * ps1-bare-metal - (C) 2023 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#pragma once\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nint isprint(int ch);\nint isgraph(int ch);\nint isspace(int ch);\nint isblank(int ch);\nint isalpha(int ch);\nint isdigit(int ch);\n\nint tolower(int ch);\nint toupper(int ch);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "src/libc/cxxsupport.cpp",
    "content": "/*\n * ps1-bare-metal - (C) 2023 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#include <stddef.h>\n#include <stdlib.h>\n\n/* Allocating new/delete operators */\n\nextern \"C\" void *__builtin_new(size_t size) {\n\treturn malloc(size);\n}\n\nextern \"C\" void __builtin_delete(void *ptr) {\n\tfree(ptr);\n}\n\nvoid *operator new(size_t size) noexcept {\n\treturn malloc(size);\n}\n\nvoid *operator new[](size_t size) noexcept {\n\treturn malloc(size);\n}\n\nvoid operator delete(void *ptr) noexcept {\n\tfree(ptr);\n}\n\nvoid operator delete[](void *ptr) noexcept {\n\tfree(ptr);\n}\n\nvoid operator delete(void *ptr, size_t size) noexcept {\n\tfree(ptr);\n}\n\nvoid operator delete[](void *ptr, size_t size) noexcept {\n\tfree(ptr);\n}\n\n/* Placement new/delete operators */\n\nvoid *operator new(size_t size, void *ptr) noexcept {\n\treturn ptr;\n}\n\nvoid *operator new[](size_t size, void *ptr) noexcept {\n\treturn ptr;\n}\n\nvoid operator delete(void *ptr, void *place) noexcept {}\n\nvoid operator delete[](void *ptr, void *place) noexcept {}\n"
  },
  {
    "path": "src/libc/malloc.c",
    "content": "/*\n * ps1-bare-metal - (C) 2023 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n *\n * This code is based on psyqo's malloc implementation, available here:\n * https://github.com/grumpycoders/pcsx-redux/blob/main/src/mips/psyqo/src/alloc.c\n */\n\n#include <stddef.h>\n#include <stdint.h>\n#include <stdlib.h>\n\n#define _align(x, n) (((x) + ((n) - 1)) & ~((n) - 1))\n#define _updateHeapUsage(incr)\n\n/* Internal state */\n\ntypedef struct _Block {\n\tstruct _Block *prev, *next;\n\n\tvoid   *ptr;\n\tsize_t size;\n} Block;\n\nstatic void  *_mallocStart;\nstatic Block *_mallocHead, *_mallocTail;\n\n/* Allocator implementation */\n\nstatic Block *_findBlock(Block *head, size_t size) {\n\tBlock *prev = head;\n\n\tfor (; prev; prev = prev->next) {\n\t\tif (prev->next) {\n\t\t\tuintptr_t nextBot = (uintptr_t) prev->next;\n\t\t\tnextBot          -= (uintptr_t) prev->ptr + prev->size;\n\n\t\t\tif (nextBot >= size)\n\t\t\t\treturn prev;\n\t\t}\n\t}\n\n\treturn prev;\n}\n\nvoid *malloc(size_t size) {\n\tif (!size)\n\t\treturn 0;\n\n\tsize_t _size = _align(size + sizeof(Block), 8);\n\n\t// Nothing's initialized yet? Let's just initialize the bottom of our heap,\n\t// flag it as allocated.\n\tif (!_mallocHead) {\n\t\tif (!_mallocStart)\n\t\t\t_mallocStart = sbrk(0);\n\n\t\tBlock *new = (Block *) sbrk(_size);\n\t\tif (!new)\n\t\t\treturn 0;\n\n\t\tvoid *ptr = (void *) &new[1];\n\t\tnew->ptr  = ptr;\n\t\tnew->size = _size - sizeof(Block);\n\t\tnew->prev = 0;\n\t\tnew->next = 0;\n\n\t\t_mallocHead = new;\n\t\t_mallocTail = new;\n\n\t\t_updateHeapUsage(size);\n\t\treturn ptr;\n\t}\n\n\t// We *may* have the bottom of our heap that has shifted, because of a free.\n\t// So let's check first if we have free space there, because I'm nervous\n\t// about having an incomplete data structure.\n\tif (((uintptr_t) _mallocStart + _size) < ((uintptr_t) _mallocHead)) {\n\t\tBlock *new = (Block *) _mallocStart;\n\n\t\tvoid *ptr = (void *) &new[1];\n\t\tnew->ptr  = ptr;\n\t\tnew->size = _size - sizeof(Block);\n\t\tnew->prev = 0;\n\t\tnew->next = _mallocHead;\n\n\t\t_mallocHead->prev = new;\n\t\t_mallocHead       = new;\n\n\t\t_updateHeapUsage(size);\n\t\treturn ptr;\n\t}\n\n\t// No luck at the beginning of the heap, let's walk the heap to find a fit.\n\tBlock *prev = _findBlock(_mallocHead, _size);\n\tif (prev) {\n\t\tBlock *new = (Block *) ((uintptr_t) prev->ptr + prev->size);\n\n\t\tvoid *ptr = (void *)((uintptr_t) new + sizeof(Block));\n\t\tnew->ptr  = ptr;\n\t\tnew->size = _size - sizeof(Block);\n\t\tnew->prev = prev;\n\t\tnew->next = prev->next;\n\n\t\t(new->next)->prev = new;\n\t\tprev->next        = new;\n\n\t\t_updateHeapUsage(size);\n\t\treturn ptr;\n\t}\n\n\t// Time to extend the size of the heap.\n\tBlock *new = (Block *) sbrk(_size);\n\tif (!new)\n\t\treturn 0;\n\n\tvoid *ptr = (void *) &new[1];\n\tnew->ptr  = ptr;\n\tnew->size = _size - sizeof(Block);\n\tnew->prev = _mallocTail;\n\tnew->next = 0;\n\n\t_mallocTail->next = new;\n\t_mallocTail       = new;\n\n\t_updateHeapUsage(size);\n\treturn ptr;\n}\n\nvoid *calloc(size_t num, size_t size) {\n\treturn malloc(num * size);\n}\n\nvoid *realloc(void *ptr, size_t size) {\n\tif (!size) {\n\t\tfree(ptr);\n\t\treturn 0;\n\t}\n\tif (!ptr)\n\t\treturn malloc(size);\n\n\tsize_t _size = _align(size + sizeof(Block), 8);\n\tBlock  *prev = (Block *) ((uintptr_t) ptr - sizeof(Block));\n\n\t// New memory block shorter?\n\tif (prev->size >= _size) {\n\t\t_updateHeapUsage(size - prev->size);\n\t\tprev->size = _size;\n\n\t\tif (!prev->next)\n\t\t\tsbrk((ptr - sbrk(0)) + _size);\n\n\t\treturn ptr;\n\t}\n\n\t// New memory block larger; is it the last one?\n\tif (!prev->next) {\n\t\tvoid *new = sbrk(_size - prev->size);\n\t\tif (!new)\n\t\t\treturn 0;\n\n\t\t_updateHeapUsage(size - prev->size);\n\t\tprev->size = _size;\n\t\treturn ptr;\n\t}\n\n\t// Do we have free memory after it?\n\tif (((prev->next)->ptr - ptr) > _size) {\n\t\t_updateHeapUsage(size - prev->size);\n\t\tprev->size = _size;\n\t\treturn ptr;\n\t}\n\n\t// No luck.\n\tvoid *new = malloc(size);\n\tif (!new)\n\t\treturn 0;\n\n\t__builtin_memcpy(new, ptr, prev->size);\n\tfree(ptr);\n\treturn new;\n}\n\nvoid free(void *ptr) {\n\tif (!ptr || !_mallocHead)\n\t\treturn;\n\n\t// First block; bumping head ahead.\n\tif (ptr == _mallocHead->ptr) {\n\t\tsize_t size = _mallocHead->size;\n\t\tsize       += (uintptr_t) _mallocHead->ptr - (uintptr_t) _mallocHead;\n\t\t_mallocHead = _mallocHead->next;\n\n\t\tif (_mallocHead) {\n\t\t\t_mallocHead->prev = 0;\n\t\t} else {\n\t\t\t_mallocTail = 0;\n\t\t\tsbrk(-size);\n\t\t}\n\n\t\t_updateHeapUsage(-(_mallocHead->size));\n\t\treturn;\n\t}\n\n\t// Finding the proper block\n\tBlock *cur = _mallocHead;\n\n\tfor (cur = _mallocHead; ptr != cur->ptr; cur = cur->next) {\n\t\tif (!cur->next)\n\t\t\treturn;\n\t}\n\n\tif (cur->next) {\n\t\t// In the middle, just unlink it\n\t\t(cur->next)->prev = cur->prev;\n\t} else {\n\t\t// At the end, shrink heap\n\t\tvoid  *top  = sbrk(0);\n\t\tsize_t size = (top - (cur->prev)->ptr) - (cur->prev)->size;\n\t\t_mallocTail = cur->prev;\n\n\t\tsbrk(-size);\n\t}\n\n\t_updateHeapUsage(-(cur->size));\n\t(cur->prev)->next = cur->next;\n}\n"
  },
  {
    "path": "src/libc/misc.c",
    "content": "/*\n * ps1-bare-metal - (C) 2023 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#include <stdio.h>\n#include \"ps1/registers.h\"\n\n/* Serial port stdin/stdout */\n\nvoid initSerialIO(int baud) {\n\tSIO_CTRL(1) = SIO_CTRL_RESET;\n\n\tSIO_MODE(1) = 0\n\t\t| SIO_MODE_BAUD_DIV1\n\t\t| SIO_MODE_DATA_8\n\t\t| SIO_MODE_STOP_1;\n\tSIO_BAUD(1) = F_CPU / baud;\n\tSIO_CTRL(1) = 0\n\t\t| SIO_CTRL_TX_ENABLE\n\t\t| SIO_CTRL_RX_ENABLE\n\t\t| SIO_CTRL_RTS;\n}\n\nvoid _putchar(char ch) {\n\t// The serial interface will buffer but not send any data if the CTS input\n\t// is not asserted, so we are going to abort if CTS is not set to avoid\n\t// waiting forever.\n\twhile (\n\t\t(SIO_STAT(1) & (SIO_STAT_TX_NOT_FULL | SIO_STAT_CTS)) == SIO_STAT_CTS\n\t)\n\t\t__asm__ volatile(\"\");\n\n\tif (SIO_STAT(1) & SIO_STAT_CTS)\n\t\tSIO_DATA(1) = ch;\n}\n\nint _getchar(void) {\n\twhile (!(SIO_STAT(1) & SIO_STAT_RX_NOT_EMPTY))\n\t\t__asm__ volatile(\"\");\n\n\treturn SIO_DATA(1);\n}\n\nint _puts(const char *str) {\n\tint length = 1;\n\n\tfor (; *str; str++, length++)\n\t\t_putchar(*str);\n\n\t_putchar('\\n');\n\treturn length;\n}\n\n/* Abort functions */\n\nvoid _assertAbort(const char *file, int line, const char *expr) {\n#ifndef NDEBUG\n\tprintf(\"%s:%d: assert(%s)\\n\", file, line, expr);\n#endif\n\n\tfor (;;)\n\t\t__asm__ volatile(\"\");\n}\n\nvoid abort(void) {\n#ifndef NDEBUG\n\tputs(\"abort()\");\n#endif\n\n\tfor (;;)\n\t\t__asm__ volatile(\"\");\n}\n\nvoid __cxa_pure_virtual(void) {\n#ifndef NDEBUG\n\tputs(\"__cxa_pure_virtual()\");\n#endif\n\n\tfor (;;)\n\t\t__asm__ volatile(\"\");\n}\n"
  },
  {
    "path": "src/libc/setjmp.h",
    "content": "/*\n * ps1-bare-metal - (C) 2023 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#pragma once\n\n#include <stdint.h>\n\ntypedef struct {\n\tuint32_t ra;\n\tuint32_t s0, s1, s2, s3, s4, s5, s6, s7;\n\tuint32_t gp, sp, fp;\n} jmp_buf;\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nint setjmp(jmp_buf *env);\nvoid longjmp(jmp_buf *env, int status);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "src/libc/setjmp.s",
    "content": "# ps1-bare-metal - (C) 2023-2025 spicyjpeg\n#\n# Permission to use, copy, modify, and/or distribute this software for any\n# purpose with or without fee is hereby granted, provided that the above\n# copyright notice and this permission notice appear in all copies.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n# PERFORMANCE OF THIS SOFTWARE.\n\n.set noreorder\n\n# This is not a \"proper\" implementation of setjmp/longjmp as it does not save\n# COP0 and GTE registers, but it is good enough for most use cases.\n\n.section .text.setjmp, \"ax\", @progbits\n.global setjmp\n.type setjmp, @function\n\nsetjmp:\n\tsw    $ra, 0x00($a0)\n\tsw    $s0, 0x04($a0)\n\tsw    $s1, 0x08($a0)\n\tsw    $s2, 0x0c($a0)\n\tsw    $s3, 0x10($a0)\n\tsw    $s4, 0x14($a0)\n\tsw    $s5, 0x18($a0)\n\tsw    $s6, 0x1c($a0)\n\tsw    $s7, 0x20($a0)\n\tsw    $gp, 0x24($a0)\n\tsw    $sp, 0x28($a0)\n\tsw    $fp, 0x2c($a0)\n\n\t# return 0;\n\tjr    $ra\n\tli    $v0, 0\n\n.section .text.longjmp, \"ax\", @progbits\n.global longjmp\n.type longjmp, @function\n\nlongjmp:\n\tlw    $ra, 0x00($a0)\n\tlw    $s0, 0x04($a0)\n\tlw    $s1, 0x08($a0)\n\tlw    $s2, 0x0c($a0)\n\tlw    $s3, 0x10($a0)\n\tlw    $s4, 0x14($a0)\n\tlw    $s5, 0x18($a0)\n\tlw    $s6, 0x1c($a0)\n\tlw    $s7, 0x20($a0)\n\tlw    $gp, 0x24($a0)\n\tlw    $sp, 0x28($a0)\n\tlw    $fp, 0x2c($a0)\n\n\t# return status;\n\tjr    $ra\n\tmove  $v0, $a1\n"
  },
  {
    "path": "src/libc/stdio.h",
    "content": "/*\n * ps1-bare-metal - (C) 2023 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#pragma once\n\n// Include printf() from the third-party library.\n#include \"vendor/printf.h\"\n\n#define putchar _putchar\n#define getchar _getchar\n#define puts    _puts\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * @brief Initializes the serial port (SIO1) with the given baud rate, no\n * parity, 8 data bits and 1 stop bit. Must be called prior to using putchar(),\n * getchar(), puts() or printf().\n *\n * @param baud\n */\nvoid initSerialIO(int baud);\n\nvoid _putchar(char ch);\nint _getchar(void);\nint _puts(const char *str);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "src/libc/stdlib.h",
    "content": "/*\n * ps1-bare-metal - (C) 2023 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#pragma once\n\n#include <stddef.h>\n#include <stdint.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nstatic inline int abs(int value) {\n\treturn (value < 0) ? (-value) : value;\n}\nstatic inline long labs(long value) {\n\treturn (value < 0) ? (-value) : value;\n}\n\nvoid abort(void);\n\nlong strtol(const char *str, char **strEnd, int base);\nlong long strtoll(const char *str, char **strEnd, int base);\n\nvoid *sbrk(ptrdiff_t incr);\n\nvoid *malloc(size_t size);\nvoid *calloc(size_t num, size_t size);\nvoid *realloc(void *ptr, size_t size);\nvoid free(void *ptr);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "src/libc/string.c",
    "content": "/*\n * ps1-bare-metal - (C) 2023 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#include <ctype.h>\n#include <stddef.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n\n/* Character manipulation */\n\nint isprint(int ch) {\n\treturn (ch >= ' ') && (ch <= '~');\n}\n\nint isgraph(int ch) {\n\treturn (ch > ' ') && (ch <= '~');\n}\n\nint isspace(int ch) {\n\treturn (ch == ' ') || ((ch >= '\\t') && (ch <= '\\r'));\n}\n\nint isblank(int ch) {\n\treturn (ch == ' ') || (ch == '\\t');\n}\n\nint isalpha(int ch) {\n\treturn ((ch >= 'A') && (ch <= 'Z')) || ((ch >= 'a') && (ch <= 'z'));\n}\n\nint isdigit(int ch) {\n\treturn (ch >= '0') && (ch <= '9');\n}\n\nint tolower(int ch) {\n\tif ((ch >= 'A') && (ch <= 'Z'))\n\t\tch += 'a' - 'A';\n\n\treturn ch;\n}\n\nint toupper(int ch) {\n\tif ((ch >= 'a') && (ch <= 'z'))\n\t\tch += 'A' - 'a';\n\n\treturn ch;\n}\n\n/* Memory buffer manipulation */\n\n#if 0\nvoid *memset(void *dest, int ch, size_t count) {\n\tuint8_t *_dest = (uint8_t *) dest;\n\n\tfor (; count; count--)\n\t\t*(_dest++) = (uint8_t) ch;\n\n\treturn dest;\n}\n\nvoid *memcpy(void *restrict dest, const void *restrict src, size_t count) {\n\tuint8_t       *_dest = (uint8_t *)       dest;\n\tconst uint8_t *_src  = (const uint8_t *) src;\n\n\tfor (; count; count--)\n\t\t*(_dest++) = *(_src++);\n\n\treturn dest;\n}\n#endif\n\nvoid *memccpy(void *restrict dest, const void *restrict src, int ch, size_t count) {\n\tuint8_t       *_dest = (uint8_t *)       dest;\n\tconst uint8_t *_src  = (const uint8_t *) src;\n\n\tfor (; count; count--) {\n\t\tuint8_t a = *(_src++);\n\n\t\t*(_dest++) = a;\n\t\tif (a == ch)\n\t\t\treturn (void *) _dest;\n\t}\n\n\treturn 0;\n}\n\nvoid *memmove(void *dest, const void *src, size_t count) {\n\tuint8_t       *_dest = (uint8_t *)       dest;\n\tconst uint8_t *_src  = (const uint8_t *) src;\n\n\tif (_dest == _src)\n\t\treturn dest;\n\tif ((_dest >= &_src[count]) || (&_dest[count] <= _src))\n\t\treturn memcpy(dest, src, count);\n\n\tif (_dest < _src) { // Copy forwards\n\t\tfor (; count; count--)\n\t\t\t*(_dest++) = *(_src++);\n\t} else { // Copy backwards\n\t\t_src  += count;\n\t\t_dest += count;\n\n\t\tfor (; count; count--)\n\t\t\t*(--_dest) = *(--_src);\n\t}\n\n\treturn dest;\n}\n\nint memcmp(const void *lhs, const void *rhs, size_t count) {\n\tconst uint8_t *_lhs = (const uint8_t *) lhs;\n\tconst uint8_t *_rhs = (const uint8_t *) rhs;\n\n\tfor (; count; count--) {\n\t\tuint8_t a = *(_lhs++), b = *(_rhs++);\n\n\t\tif (a != b)\n\t\t\treturn ((int) a) - ((int) b);\n\t}\n\n\treturn 0;\n}\n\nvoid *memchr(const void *ptr, int ch, size_t count) {\n\tconst uint8_t *_ptr = (const uint8_t *) ptr;\n\n\tfor (; count; count--, _ptr++) {\n\t\tif (*_ptr == ch)\n\t\t\treturn (void *) _ptr;\n\t}\n\n\treturn 0;\n}\n\n/* String manipulation */\n\nchar *strcpy(char *restrict dest, const char *restrict src) {\n\tchar *_dest = dest;\n\n\twhile (*src)\n\t\t*(_dest++) = *(src++);\n\n\t*_dest = 0;\n\treturn dest;\n}\n\nchar *strncpy(char *restrict dest, const char *restrict src, size_t count) {\n\tchar *_dest = dest;\n\n\tfor (; count && *src; count--)\n\t\t*(_dest++) = *(src++);\n\tfor (; count; count--)\n\t\t*(_dest++) = 0;\n\n\treturn dest;\n}\n\nint strcmp(const char *lhs, const char *rhs) {\n\tfor (;;) {\n\t\tchar a = *(lhs++), b = *(rhs++);\n\n\t\tif (a != b)\n\t\t\treturn ((int) a) - ((int) b);\n\t\tif (!a && !b)\n\t\t\treturn 0;\n\t}\n}\n\nint strncmp(const char *lhs, const char *rhs, size_t count) {\n\tfor (; count && *lhs && *rhs; count--) {\n\t\tchar a = *(lhs++), b = *(rhs++);\n\n\t\tif (a != b)\n\t\t\treturn ((int) a) - ((int) b);\n\t}\n\n\treturn 0;\n}\n\nchar *strchr(const char *str, int ch) {\n\tfor (; *str; str++) {\n\t\tif (*str == ch)\n\t\t\treturn (char *) str;\n\t}\n\n\treturn 0;\n}\n\nchar *strrchr(const char *str, int ch) {\n\tsize_t length = strlen(str);\n\n\tfor (str += length; length; length--) {\n\t\tstr--;\n\t\tif (*str == ch)\n\t\t\treturn (char *) str;\n\t}\n\n\treturn 0;\n}\n\nchar *strpbrk(const char *str, const char *breakSet) {\n\tfor (; *str; str++) {\n\t\tchar a = *str;\n\n\t\tfor (const char *ch = breakSet; *ch; ch++) {\n\t\t\tif (a == *ch)\n\t\t\t\treturn (char *) str;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nchar *strstr(const char *str, const char *substr) {\n\tsize_t length = strlen(substr);\n\n\tif (!length)\n\t\treturn (char *) str;\n\n\tfor (; *str; str++) {\n\t\tif (!memcmp(str, substr, length))\n\t\t\treturn (char *) str;\n\t}\n\n\treturn 0;\n}\n\nsize_t strlen(const char *str) {\n\tsize_t length = 0;\n\n\tfor (; *str; str++)\n\t\tlength++;\n\n\treturn length;\n}\n\n// Non-standard, used internally\nsize_t strnlen(const char *str, size_t count) {\n\tsize_t length = 0;\n\n\tfor (; *str && (length < count); str++)\n\t\tlength++;\n\n\treturn length;\n}\n\nchar *strcat(char *restrict dest, const char *restrict src) {\n\tchar *_dest = &dest[strlen(dest)];\n\n\twhile (*src)\n\t\t*(_dest++) = *(src++);\n\n\t*_dest = 0;\n\treturn dest;\n}\n\nchar *strncat(char *restrict dest, const char *restrict src, size_t count) {\n\tchar *_dest = &dest[strlen(dest)];\n\n\tfor (; count && *src; count--)\n\t\t*(_dest++) = *(src++);\n\n\t*_dest = 0;\n\treturn dest;\n}\n\nchar *strdup(const char *str) {\n\tsize_t length = strlen(str) + 1;\n\tchar   *copy  = malloc(length);\n\n\tif (!copy)\n\t\treturn 0;\n\n\tmemcpy(copy, str, length);\n\treturn copy;\n}\n\nchar *strndup(const char *str, size_t count) {\n\tsize_t length = strnlen(str, count) + 1;\n\tchar   *copy  = malloc(length);\n\n\tif (!copy)\n\t\treturn 0;\n\n\tmemcpy(copy, str, length);\n\treturn copy;\n}\n\n/* String tokenizer */\n\nstatic char *_strtokPtr = 0, *_strtokEndPtr = 0;\n\nchar *strtok(char *restrict str, const char *restrict delim) {\n\tif (str) {\n\t\t_strtokPtr    = str;\n\t\t_strtokEndPtr = &str[strlen(str)];\n\t}\n\n\tif (_strtokPtr >= _strtokEndPtr)\n\t\treturn 0;\n\tif (!(*_strtokPtr))\n\t\treturn 0;\n\n\tchar *split = strstr(_strtokPtr, delim);\n\tchar *token = _strtokPtr;\n\n\tif (split) {\n\t\t*(split++)  = 0;\n\t\t_strtokPtr = split;\n\t} else {\n\t\t_strtokPtr += strlen(token);\n\t}\n\n\treturn token;\n}\n\n/* Number parsers */\n\nlong long strtoll(const char *restrict str, char **restrict strEnd, int base) {\n\tif (!str)\n\t\treturn 0;\n\twhile (isspace(*str))\n\t\tstr++;\n\n\tchar sign = *str;\n\n\tif ((sign == '+') || (sign == '-'))\n\t\tstr++;\n\twhile (isspace(*str))\n\t\tstr++;\n\n\t// Parse any base prefix if present. If a base was specified make sure it\n\t// matches, otherwise use it to determine which base the value is in.\n\tlong long value = 0;\n\n\tif (*str == '0') {\n\t\tint foundBase;\n\n\t\tswitch (str[1]) {\n\t\t\tcase 0:\n\t\t\t\tgoto _exit;\n\n\t\t\tcase 'X':\n\t\t\tcase 'x':\n\t\t\t\tfoundBase = 16;\n\t\t\t\tstr      += 2;\n\t\t\t\tbreak;\n\n\t\t\tcase 'O':\n\t\t\tcase 'o':\n\t\t\t\tfoundBase = 8;\n\t\t\t\tstr      += 2;\n\t\t\t\tbreak;\n\n\t\t\tcase 'B':\n\t\t\tcase 'b':\n\t\t\t\tfoundBase = 2;\n\t\t\t\tstr      += 2;\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\t// Numbers starting with a zero are *not* interpreted as octal\n\t\t\t\t// unless base = 8.\n\t\t\t\tfoundBase = 0;\n\t\t\t\tstr++;\n\t\t}\n\n\t\tif (!base)\n\t\t\tbase = foundBase;\n\t\telse if (foundBase && (base != foundBase))\n\t\t\treturn 0;\n\t}\n\n\tif (!base)\n\t\tbase = 10;\n\telse if ((base < 2) || (base > 36))\n\t\treturn 0;\n\n\t// Parse the actual value.\n\tfor (; *str; str++) {\n\t\tchar ch = *str;\n\t\tint  digit;\n\n\t\tswitch (ch) {\n\t\t\tcase '0' ... '9':\n\t\t\t\tdigit = ch - '0';\n\t\t\t\tbreak;\n\n\t\t\tcase 'A' ... 'Z':\n\t\t\t\tdigit = (ch - 'A') + 10;\n\t\t\t\tbreak;\n\n\t\t\tcase 'a' ... 'z':\n\t\t\t\tdigit = (ch - 'a') + 10;\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tgoto _exit;\n\t\t}\n\n\t\tvalue = (value * base) + digit;\n\t}\n\n_exit:\n\tif (strEnd)\n\t\t*strEnd = (char *) str;\n\n\treturn (sign == '-') ? (-value) : value;\n}\n\nlong strtol(const char *restrict str, char **restrict strEnd, int base) {\n\treturn (long) strtoll(str, strEnd, base);\n}\n"
  },
  {
    "path": "src/libc/string.h",
    "content": "/*\n * ps1-bare-metal - (C) 2023 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#pragma once\n\n#include <stddef.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid *memset(void *dest, int ch, size_t count);\nvoid *memcpy(void *dest, const void *src, size_t count);\nvoid *memccpy(void *dest, const void *src, int ch, size_t count);\nvoid *memmove(void *dest, const void *src, size_t count);\nint memcmp(const void *lhs, const void *rhs, size_t count);\nvoid *memchr(const void *ptr, int ch, size_t count);\n\nchar *strcpy(char *dest, const char *src);\nchar *strncpy(char *dest, const char *src, size_t count);\nint strcmp(const char *lhs, const char *rhs);\nint strncmp(const char *lhs, const char *rhs, size_t count);\nchar *strchr(const char *str, int ch);\nchar *strrchr(const char *str, int ch);\nchar *strpbrk(const char *str, const char *breakSet);\nchar *strstr(const char *str, const char *substr);\n\nsize_t strlen(const char *str);\nchar *strcat(char *dest, const char *src);\nchar *strncat(char *dest, const char *src, size_t count);\nchar *strdup(const char *str);\nchar *strndup(const char *str, size_t count);\n\nchar *strtok(char *str, const char *delim);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "src/libc/string.s",
    "content": "# ps1-bare-metal - (C) 2023-2025 spicyjpeg\n#\n# Permission to use, copy, modify, and/or distribute this software for any\n# purpose with or without fee is hereby granted, provided that the above\n# copyright notice and this permission notice appear in all copies.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n# PERFORMANCE OF THIS SOFTWARE.\n\n.set noreorder\n.set noat\n\n# This file contains optimized implementations of memset() and memcpy() that\n# make use of unrolled loops, Duff's device and unaligned load/store opcodes to\n# fill large areas of memory much faster than a simple byte-by-byte loop would.\n\n.set LARGE_FILL_THRESHOLD, 32\n.set LARGE_COPY_THRESHOLD, 32\n\n.set temp,      $at\n.set destCopy,  $v0\n.set remainder, $v1\n\n.set dest,       $a0\n.set value,      $a1 # memset()\n.set source,     $a1 # memcpy()\n.set length,     $a2\n.set instLength, $a3 # memcpy()\n\n.set value0,       $t0\n.set value1,       $t1\n.set value2,       $t2\n.set value3,       $t3\n.set value4,       $t4\n.set value5,       $t5\n.set value6,       $t6\n.set value7,       $t7\n.set loadJumpPtr,  $t8\n.set storeJumpPtr, $t9\n\n.section .text.memset, \"ax\", @progbits\n.global memset\n.type memset, @function\n\nmemset:\n\t# If more than 32 bytes have to be written then take the \"large\" path,\n\t# otherwise perform a byte-by-byte fill (which will typically end up faster\n\t# due to the loop being able to run entirely from the instruction cache).\n\taddiu temp, length, -LARGE_FILL_THRESHOLD\n\tbgtz  temp, .LlargeFill\n\tmove  destCopy, dest\n\n\tblez  length, .LsmallFillDone\n\tnop\n\n.LsmallFillLoop: # for (; length > 0; length--) {\n\t# *(dest++) = value;\n\tsb    value, 0(dest)\n\taddiu length, -1\n\tbgtz  length, .LsmallFillLoop\n\taddiu dest, 1\n\n.LsmallFillDone: # }\n\t# return destCopy;\n\tjr    $ra\n\tnop\n\n.LlargeFill:\n\t# Initialize fast filling by repeating the fill byte 4 times, so it can be\n\t# written 32 bits at a time.\n\n\t# value &= 0xff;\n\t# value |= value <<  8;\n\t# value |= value << 16;\n\tandi  value, 0xff\n\tsll   temp, value, 8\n\tor    value, temp\n\tsll   temp, value, 16\n\tor    value, temp\n\n\t# Fill the first 1-4 bytes quickly using an unaligned store.\n\tswr   value, 0(dest)\n\n\t# int filled = 4 - (dest % 4);\n\t# dest      += filled;\n\t# length    -= filled;\n\tandi  temp, dest, 3\n\taddiu temp, -4\n\tsubu  dest, temp\n\taddu  length, temp\n\n\t# int remainder = length % 4;\n\t# length       -= remainder;\n\tandi  remainder, length, 3\n\tsubu  length, remainder\n\n\tla    storeJumpPtr, .LlargeFillDuff\n\n.LlargeFillLoop: # while (length > 0) {\n\t# If 64 bytes or more still have to be written, skip calculating the jump\n\t# offset and execute the whole block of sw opcodes. Otherwise, adjust the\n\t# destination pointer and jump into the middle of the block, using the\n\t# length as an index. No multiplication or division is needed as each\n\t# instruction is 4 bytes long and also writes 4 bytes.\n\taddiu length, -64\n\tbgez  length, .LlargeFillDuff\n\tsubu  temp, storeJumpPtr, length\n\n\t# dest -= 64 - length;\n\t# goto &largeFillDuff[(64 - length) / 4 * 4];\n\tjr    temp\n\taddu  dest, length\n\n.LlargeFillDuff:\n\tsw    value, 0x00(dest)\n\tsw    value, 0x04(dest)\n\tsw    value, 0x08(dest)\n\tsw    value, 0x0c(dest)\n\tsw    value, 0x10(dest)\n\tsw    value, 0x14(dest)\n\tsw    value, 0x18(dest)\n\tsw    value, 0x1c(dest)\n\tsw    value, 0x20(dest)\n\tsw    value, 0x24(dest)\n\tsw    value, 0x28(dest)\n\tsw    value, 0x2c(dest)\n\tsw    value, 0x30(dest)\n\tsw    value, 0x34(dest)\n\tsw    value, 0x38(dest)\n\tsw    value, 0x3c(dest)\n\n\t# length -= 64;\n\t# dest   += 64;\n\t# }\n\tbgtz  length, .LlargeFillLoop\n\taddiu dest, 64\n\n\t# Fill the remaining 1-4 bytes using another unaligned store.\n\taddu  dest, remainder\n\n\t# return destCopy;\n\tjr    $ra\n\tswl   value, -1(dest)\n\n.section .text.memcpy, \"ax\", @progbits\n.global memcpy\n.type memcpy, @function\n\nmemcpy:\n\t# If more than 32 bytes have to be written then take the \"large\" path,\n\t# otherwise perform a byte-by-byte copy.\n\taddiu temp, length, -LARGE_COPY_THRESHOLD\n\tbgtz  temp, .LlargeCopy\n\tmove  destCopy, dest\n\n\tblez  length, .LsmallCopyDone\n\tnop\n\n.LsmallCopyLoop: # for (; length > 0; length--) {\n\t# *(dest++) = *(source++);\n\tlbu   value0, 0(source)\n\taddiu length, -1\n\tsb    value0, 0(dest)\n\taddiu source, 1\n\tbgtz  length, .LsmallCopyLoop\n\taddiu dest, 1\n\n.LsmallCopyDone: # }\n\t# return destCopy;\n\tjr    $ra\n\tnop\n\n.LlargeCopy:\n\t# Copy the first 4 bytes and realign the source pointer to the closest\n\t# 4-byte boundary.\n\tlwr   value0, 0(source)\n\tlwl   value0, 3(source)\n\tandi  temp, source, 3\n\tswr   value0, 0(dest)\n\tswl   value0, 3(dest)\n\n\t# int copied = 4 - (source % 4);\n\t# source    += copied;\n\t# dest      += copied;\n\t# length    -= copied;\n\taddiu temp, -4\n\tsubu  source, temp\n\tsubu  dest, temp\n\taddu  length, temp\n\n\t# int remainder = length % 4;\n\t# length       -= remainder;\n\tandi  remainder, length, 3\n\tsubu  length, remainder\n\n\t# Check if the adjusted destination pointer is also aligned. If not, use the\n\t# alternate code block that performs unaligned stores in place of aligned\n\t# ones.\n\n\t# void *storeJumpPtr = (dest % 4) ? largeUnalignedStoreDuff : largeAlignedStoreDuff;\n\t# int  instLength    = (dest % 4) ? 8 : 4;\n\tla    loadJumpPtr, .LlargeLoadDuff\n\taddiu storeJumpPtr, loadJumpPtr, .LlargeAlignedStoreDuff - .LlargeLoadDuff\n\tandi  temp, dest, 3\n\tbeqz  temp, .LlargeCopyLoop\n\tli    instLength, 0\n\n\taddiu storeJumpPtr, loadJumpPtr, .LlargeUnalignedStoreDuff - .LlargeLoadDuff\n\tli    instLength, 1\n\n.LlargeCopyLoop: # while (length > 0) {\n\t# If 32 bytes or more still have to be written, skip calculating the jump\n\t# offset and execute the whole block of lw opcodes. Otherwise, adjust the\n\t# destination pointer and jump into the middle of the block, using the\n\t# length as an index.\n\taddiu length, -32\n\tbgez  length, .LlargeLoadDuff\n\tsubu  temp, loadJumpPtr, length\n\n\t# source -= 32 - length;\n\t# goto &LlargeLoadDuff[(32 - length) / 4 * 4];\n\tjr    temp\n\taddu  source, length\n\n.LlargeLoadDuff:\n\tlw    value0, 0x00(source)\n\tlw    value1, 0x04(source)\n\tlw    value2, 0x08(source)\n\tlw    value3, 0x0c(source)\n\tlw    value4, 0x10(source)\n\tlw    value5, 0x14(source)\n\tlw    value6, 0x18(source)\n\tlw    value7, 0x1c(source)\n\n\t# Do the same for the block of store instructions. An extra branch is needed\n\t# here for the >=32 byte case due to there being no conditional variant of\n\t# the jr instruction.\n\tbgez  length, .LjumpToLargeStoreDuff\n\taddiu source, 32\n\n\t# dest -= 32 - length;\n\t# goto &storeJumpPtr[(32 - length) / 4 * instLength];\n\tsllv  temp, length, instLength\n\tsubu  temp, storeJumpPtr, temp\n\tjr    temp\n\taddu  dest, length\n\n.LjumpToLargeStoreDuff:\n\tjr    storeJumpPtr\n\tnop\n\n.LlargeUnalignedStoreDuff:\n\tswr   value0, 0x00(dest)\n\tswl   value0, 0x03(dest)\n\tswr   value1, 0x04(dest)\n\tswl   value1, 0x07(dest)\n\tswr   value2, 0x08(dest)\n\tswl   value2, 0x0b(dest)\n\tswr   value3, 0x0c(dest)\n\tswl   value3, 0x0f(dest)\n\tswr   value4, 0x10(dest)\n\tswl   value4, 0x13(dest)\n\tswr   value5, 0x14(dest)\n\tswl   value5, 0x17(dest)\n\tswr   value6, 0x18(dest)\n\tswl   value6, 0x1b(dest)\n\tswr   value7, 0x1c(dest)\n\tswl   value7, 0x1f(dest)\n\n\tbgtz  length, .LlargeCopyLoop\n\taddiu dest, 32\n\n\tb     .LlargeCopyDone\n\taddu  source, remainder\n\n.LlargeAlignedStoreDuff:\n\tsw    value0, 0x00(dest)\n\tsw    value1, 0x04(dest)\n\tsw    value2, 0x08(dest)\n\tsw    value3, 0x0c(dest)\n\tsw    value4, 0x10(dest)\n\tsw    value5, 0x14(dest)\n\tsw    value6, 0x18(dest)\n\tsw    value7, 0x1c(dest)\n\n\t# length -= 32;\n\t# source += 32;\n\t# dest   += 32;\n\t# }\n\tbgtz  length, .LlargeCopyLoop\n\taddiu dest, 32\n\n\taddu  source, remainder\n\n.LlargeCopyDone:\n\t# Copy the last 4 bytes. This may end up overlapping the last word copied,\n\t# but it is still the fastest way to flush any remaining bytes.\n\tlwr   value0, -4(source)\n\tlwl   value0, -1(source)\n\taddu  dest, remainder\n\tswr   value0, -4(dest)\n\n\t# return destCopy;\n\tjr    $ra\n\tswl   value0, -1(dest)\n"
  },
  {
    "path": "src/ps1/cache.h",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#pragma once\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid flushCache(void);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "src/ps1/cache.s",
    "content": "# ps1-bare-metal - (C) 2023-2025 spicyjpeg\n#\n# Permission to use, copy, modify, and/or distribute this software for any\n# purpose with or without fee is hereby granted, provided that the above\n# copyright notice and this permission notice appear in all copies.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n# PERFORMANCE OF THIS SOFTWARE.\n\n.set noreorder\n\n# This is a minimal implementation of a function to flush the CPU's instruction\n# cache. While a similar function is already present in the BIOS ROM, this\n# version is several orders of magnitude faster as it only clears the tags (the\n# BIOS implementation unnecessarily zeroes out the contents of every cache line)\n# and is not subject to the waitstates imposed by the ROM's 8-bit bus.\n\n.set KSEG1_BASE, 0xa0000000\n.set CPU_BCC,    0xfffe0130\n\n.set CPU_BCC_TAG, 1 <<  2\n.set CPU_BCC_DS,  1 <<  7\n.set CPU_BCC_IS1, 1 << 11\n\n.set COP0_STATUS, $12\n\n.set COP0_STATUS_IsC, 1 << 16\n\n.set ptr,         $a0\n.set temp,        $a1\n.set savedStatus, $a2\n.set savedBCC,    $a3\n\n.section .text.flushCache, \"ax\", @progbits\n.global flushCache\n.type flushCache, @function\n\nflushCache:\n\t# Call _flushCacheInner() through the uncached KSEG1 mirror of main RAM,\n\t# ensuring the CPU will not attempt to use the cache while it is being\n\t# cleared. This jump must be performed using a la/jr combo as immediate\n\t# jumps (j) only update the program counter's bottommost 28 bits.\n\tla    ptr, _flushCacheInner\n\tlui   temp, %hi(KSEG1_BASE)\n\tor    ptr, temp\n\n\tjr    ptr\n\tlui   ptr, %hi(CPU_BCC)\n\n.section .text._flushCacheInner, \"ax\", @progbits\n.type _flushCacheInner, @function\n\n_flushCacheInner:\n\t# Save the current state of the BCC and COP0 status registers so that they\n\t# can be restored later.\n\tmfc0  savedStatus, COP0_STATUS\n\tlw    savedBCC, %lo(CPU_BCC)(ptr)\n\n\t# Disable interrupts and the scratchpad, put the instruction cache into \"tag\n\t# test\" mode and proceed to map it directly to the CPU's address space by\n\t# setting the COP0 \"isolate cache\" flag.\n\tmtc0  $0, COP0_STATUS\n\n\t# CPU_BCC = (CPU_BCC & ~CPU_BCC_DS) | CPU_BCC_TAG | CPU_BCC_IS1;\n\tli    temp, ~CPU_BCC_DS\n\tand   temp, savedBCC\n\tori   temp, CPU_BCC_TAG | CPU_BCC_IS1\n\tsw    temp, %lo(CPU_BCC)(ptr)\n\n\t# cop0_setReg(COP0_STATUS, COP0_STATUS_IsC);\n\tli    temp, COP0_STATUS_IsC\n\tmtc0  temp, COP0_STATUS\n\n\t# Use an unrolled loop to clear all tags, thus invalidating the cache's\n\t# contents. \"Tag test\" mode maps each tag into memory at the offset of its\n\t# respective 16-byte cache line.\n\tli    temp, 0x1000 - 256\n\n.LclearLoop: # for (int i = 0x1000 - 256; i >= 0; i -= 256) {\n\tsw    $0, 0x00(temp)\n\tsw    $0, 0x10(temp)\n\tsw    $0, 0x20(temp)\n\tsw    $0, 0x30(temp)\n\tsw    $0, 0x40(temp)\n\tsw    $0, 0x50(temp)\n\tsw    $0, 0x60(temp)\n\tsw    $0, 0x70(temp)\n\tsw    $0, 0x80(temp)\n\tsw    $0, 0x90(temp)\n\tsw    $0, 0xa0(temp)\n\tsw    $0, 0xb0(temp)\n\tsw    $0, 0xc0(temp)\n\tsw    $0, 0xd0(temp)\n\tsw    $0, 0xe0(temp)\n\tsw    $0, 0xf0(temp)\n\n\t# }\n\tbgtz  temp, .LclearLoop\n\taddiu temp, -256\n\n\t# Clear the \"isolate cache\" bit, restore the previously saved state and\n\t# return.\n\tmtc0  $0, COP0_STATUS\n\tnop\n\tsw    savedBCC, %lo(CPU_BCC)(ptr)\n\tmtc0  savedStatus, COP0_STATUS\n\n\tjr    $ra\n\tnop\n"
  },
  {
    "path": "src/ps1/cdrom.h",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#pragma once\n\n#include <stdint.h>\n\n#define DEF(type) static inline type __attribute__((always_inline))\n\n/* CD-ROM XA sector header structure */\n\ntypedef struct __attribute__((packed)) {\n\tuint8_t file, channel, submode, codingInfo;\n} CDROMXAHeader;\n\ntypedef enum {\n\tCDROM_XA_SM_END_OF_RECORD = 1 << 0,\n\tCDROM_XA_SM_TYPE_VIDEO    = 1 << 1,\n\tCDROM_XA_SM_TYPE_AUDIO    = 1 << 2,\n\tCDROM_XA_SM_TYPE_DATA     = 1 << 3,\n\tCDROM_XA_SM_TRIGGER       = 1 << 4,\n\tCDROM_XA_SM_FORM2         = 1 << 5,\n\tCDROM_XA_SM_REAL_TIME     = 1 << 6,\n\tCDROM_XA_SM_END_OF_FILE   = 1 << 7\n} CDROMXASubmodeFlag;\n\ntypedef enum {\n\tCDROM_XA_CI_STEREO              = 1 << 0,\n\tCDROM_XA_CI_SAMPLE_RATE_BITMASK = 1 << 2,\n\tCDROM_XA_CI_SAMPLE_RATE_18900   = 0 << 2,\n\tCDROM_XA_CI_SAMPLE_RATE_37800   = 1 << 2,\n\tCDROM_XA_CI_BITS_BITMASK        = 1 << 4,\n\tCDROM_XA_CI_BITS_4              = 0 << 4,\n\tCDROM_XA_CI_BITS_8              = 1 << 4,\n\tCDROM_XA_CI_EMPHASIS            = 1 << 6\n} CDROMXACodingInfoFlag;\n\n/* CD-ROM drive data types */\n\ntypedef struct __attribute__((packed)) {\n\tuint8_t minute, second, frame;\n} CDROMMSF;\n\ntypedef struct __attribute__((packed)) {\n\tCDROMMSF      absoluteMSF;\n\tuint8_t       mode;\n\tCDROMXAHeader header;\n} CDROMGetlocLResult;\n\ntypedef struct __attribute__((packed)) {\n\tuint8_t  track, index;\n\tCDROMMSF relativeMSF, absoluteMSF;\n} CDROMGetlocPResult;\n\ntypedef struct __attribute__((packed)) {\n\tuint8_t status, flag, type, atip;\n\tchar    license[4];\n} CDROMGetIDResult;\n\ntypedef struct __attribute__((packed)) {\n\tuint8_t  status, track, index;\n\tCDROMMSF msf;\n\tuint16_t peak;\n} CDROMReportPacket;\n\nDEF(uint8_t) cdrom_encodeBCD(uint8_t value) {\n\t// output = units + tens * 16\n\t//        = units + tens * 10 + tens * 6\n\t//        = value             + tens * 6\n\treturn value + (value / 10) * 6;\n}\n\nDEF(uint8_t) cdrom_decodeBCD(uint8_t value) {\n\t// output = low + high * 10\n\t//        = low + high * 16 - high * 6\n\t//        = value           - high * 6\n\treturn value - (value >> 4) * 6;\n}\n\nDEF(void) cdrom_convertLBAToMSF(CDROMMSF *msf, unsigned int lba) {\n\tlba += 150; // Skip lead-in area (LBA 0 is always at 00:02:00)\n\n\tmsf->minute = cdrom_encodeBCD( lba / (75 * 60));\n\tmsf->second = cdrom_encodeBCD((lba / 75) % 60);\n\tmsf->frame  = cdrom_encodeBCD( lba % 75);\n}\n\nDEF(unsigned int) cdrom_convertMSFToLBA(const CDROMMSF *msf) {\n\treturn 0\n\t\t+ cdrom_decodeBCD(msf->minute) * (75 * 60)\n\t\t+ cdrom_decodeBCD(msf->second) * 75\n\t\t+ cdrom_decodeBCD(msf->frame)\n\t\t- 150;\n}\n\n/* CD-ROM drive command and status definitions */\n\ntypedef enum {\n\tCDROM_CMD_NOP        = 0x01,\n\tCDROM_CMD_SETLOC     = 0x02,\n\tCDROM_CMD_PLAY       = 0x03,\n\tCDROM_CMD_FORWARD    = 0x04,\n\tCDROM_CMD_BACKWARD   = 0x05,\n\tCDROM_CMD_READ_N     = 0x06,\n\tCDROM_CMD_STANDBY    = 0x07,\n\tCDROM_CMD_STOP       = 0x08,\n\tCDROM_CMD_PAUSE      = 0x09,\n\tCDROM_CMD_INIT       = 0x0a,\n\tCDROM_CMD_MUTE       = 0x0b,\n\tCDROM_CMD_DEMUTE     = 0x0c,\n\tCDROM_CMD_SETFILTER  = 0x0d,\n\tCDROM_CMD_SETMODE    = 0x0e,\n\tCDROM_CMD_GETPARAM   = 0x0f,\n\tCDROM_CMD_GETLOC_L   = 0x10,\n\tCDROM_CMD_GETLOC_P   = 0x11,\n\tCDROM_CMD_SETSESSION = 0x12,\n\tCDROM_CMD_GET_TN     = 0x13,\n\tCDROM_CMD_GET_TD     = 0x14,\n\tCDROM_CMD_SEEK_L     = 0x15,\n\tCDROM_CMD_SEEK_P     = 0x16,\n\tCDROM_CMD_TEST       = 0x19,\n\tCDROM_CMD_GET_ID     = 0x1a,\n\tCDROM_CMD_READ_S     = 0x1b,\n\tCDROM_CMD_RESET      = 0x1c,\n\tCDROM_CMD_GET_Q      = 0x1d, // Versions 0xc1 and later only\n\tCDROM_CMD_READ_TOC   = 0x1e, // Versions 0xc1 and later only\n\tCDROM_CMD_UNLOCK0    = 0x50, // Versions 0xc1 and later only\n\tCDROM_CMD_UNLOCK1    = 0x51, // Versions 0xc1 and later only\n\tCDROM_CMD_UNLOCK2    = 0x52, // Versions 0xc1 and later only\n\tCDROM_CMD_UNLOCK3    = 0x53, // Versions 0xc1 and later only\n\tCDROM_CMD_UNLOCK4    = 0x54, // Versions 0xc1 and later only\n\tCDROM_CMD_UNLOCK5    = 0x55, // Versions 0xc1 and later only\n\tCDROM_CMD_UNLOCK6    = 0x56, // Versions 0xc1 and later only\n\tCDROM_CMD_LOCK       = 0x57  // Versions 0xc1 and later only\n} CDROMCommand;\n\ntypedef enum {\n\tCDROM_TEST_READ_ID              = 0x04,\n\tCDROM_TEST_GET_ID_COUNTERS      = 0x05,\n\tCDROM_TEST_GET_VERSION          = 0x20,\n\tCDROM_TEST_GET_SWITCHES         = 0x21,\n\tCDROM_TEST_GET_REGION           = 0x22, // Versions 0xc1 and later only\n\tCDROM_TEST_GET_SERVO_TYPE       = 0x23, // Versions 0xc1 and later only\n\tCDROM_TEST_GET_DSP_TYPE         = 0x24, // Versions 0xc1 and later only\n\tCDROM_TEST_GET_DECODER_TYPE     = 0x25, // Versions 0xc1 and later only\n\tCDROM_TEST_DSP_CMD              = 0x50,\n\tCDROM_TEST_DSP_CMD_RESP         = 0x51, // Versions 0xc2 and later only\n\tCDROM_TEST_MCU_PEEK             = 0x60,\n\tCDROM_TEST_DECODER_GET_REG      = 0x71, // Versions 0xc1 and later only\n\tCDROM_TEST_DECODER_SET_REG      = 0x72, // Versions 0xc1 and later only\n\tCDROM_TEST_DECODER_GET_SRAM_PTR = 0x75, // Versions 0xc1 and later only\n\tCDROM_TEST_DECODER_SET_SRAM_PTR = 0x76  // Versions 0xc1 and later only\n} CDROMTestCommand;\n\ntypedef enum {\n\tCDROM_IRQ_NONE        = 0,\n\tCDROM_IRQ_DATA_READY  = 1,\n\tCDROM_IRQ_COMPLETE    = 2,\n\tCDROM_IRQ_ACKNOWLEDGE = 3,\n\tCDROM_IRQ_DATA_END    = 4,\n\tCDROM_IRQ_ERROR       = 5\n} CDROMIRQType;\n\ntypedef enum {\n\tCDROM_CMD_STAT_ERROR      = 1 << 0,\n\tCDROM_CMD_STAT_SPINDLE_ON = 1 << 1,\n\tCDROM_CMD_STAT_SEEK_ERROR = 1 << 2,\n\tCDROM_CMD_STAT_ID_ERROR   = 1 << 3,\n\tCDROM_CMD_STAT_LID_OPEN   = 1 << 4,\n\tCDROM_CMD_STAT_READING    = 1 << 5,\n\tCDROM_CMD_STAT_SEEKING    = 1 << 6,\n\tCDROM_CMD_STAT_PLAYING    = 1 << 7\n} CDROMCommandStatusFlag;\n\ntypedef enum {\n\tCDROM_CMD_ERR_SEEK_FAILED         = 1 << 2,\n\tCDROM_CMD_ERR_LID_OPENED          = 1 << 3,\n\tCDROM_CMD_ERR_INVALID_PARAM_VALUE = 1 << 4,\n\tCDROM_CMD_ERR_INVALID_PARAM_COUNT = 1 << 5,\n\tCDROM_CMD_ERR_INVALID_COMMAND     = 1 << 6,\n\tCDROM_CMD_ERR_NO_DISC             = 1 << 7\n} CDROMCommandErrorFlag;\n\ntypedef enum {\n\tCDROM_MODE_CDDA         = 1 << 0,\n\tCDROM_MODE_AUTO_PAUSE   = 1 << 1,\n\tCDROM_MODE_CDDA_REPORT  = 1 << 2,\n\tCDROM_MODE_XA_FILTER    = 1 << 3,\n\tCDROM_MODE_SIZE_BITMASK = 3 << 4,\n\tCDROM_MODE_SIZE_2048    = 0 << 4,\n\tCDROM_MODE_SIZE_2340    = 2 << 4,\n\tCDROM_MODE_XA_ADPCM     = 1 << 6,\n\tCDROM_MODE_SPEED_1X     = 0 << 7,\n\tCDROM_MODE_SPEED_2X     = 1 << 7\n} CDROMModeFlag;\n\n#undef DEF\n"
  },
  {
    "path": "src/ps1/cop0.h",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#pragma once\n\n#include <stdint.h>\n\n#define DEF(type) static inline type __attribute__((always_inline))\n\n/* Register definitions */\n\ntypedef enum {\n\tCOP0_BPC      =  3, // Breakpoint program counter (CW33300)\n\tCOP0_BDA      =  5, // Breakpoint data address (CW33300)\n\tCOP0_DCIC     =  7, // Debug and cache invalidation control (CW33300)\n\tCOP0_BADVADDR =  8, // Bad virtual address\n\tCOP0_BDAM     =  9, // Breakpoint data address mask (CW33300)\n\tCOP0_BPCM     = 11, // Breakpoint program counter mask (CW33300)\n\tCOP0_STATUS   = 12, // Status register\n\tCOP0_CAUSE    = 13, // Exception cause\n\tCOP0_EPC      = 14, // Exception program counter\n\tCOP0_PRID     = 15  // Processor identifier\n} COP0Register;\n\ntypedef enum {\n\tCOP0_DCIC_DB  = 1 <<  0, // Debug event pending\n\tCOP0_DCIC_PC  = 1 <<  1, // Program counter breakpoint pending\n\tCOP0_DCIC_DA  = 1 <<  2, // Data address breakpoint pending\n\tCOP0_DCIC_R   = 1 <<  3, // Data address read breakpoint pending\n\tCOP0_DCIC_W   = 1 <<  4, // Data address write breakpoint pending\n\tCOP0_DCIC_T   = 1 <<  5, // Trace event pending\n\tCOP0_DCIC_DE  = 1 << 23, // Debug enable\n\tCOP0_DCIC_PCE = 1 << 24, // Program counter breakpoint enable\n\tCOP0_DCIC_DAE = 1 << 25, // Data address breakpoint enable\n\tCOP0_DCIC_DR  = 1 << 26, // Data address read breakpoint enable\n\tCOP0_DCIC_DW  = 1 << 27, // Data address write breakpoint enable\n\tCOP0_DCIC_TE  = 1 << 28, // Trace enable\n\tCOP0_DCIC_KD  = 1 << 29, // Kernel debug enable\n\tCOP0_DCIC_UD  = 1 << 30, // User debug enable\n\tCOP0_DCIC_TR  = 1 << 31  // Debug event trap enable\n} COP0DCICFlag;\n\ntypedef enum {\n\tCOP0_STATUS_IEc = 1 <<  0, // Current interrupt enable\n\tCOP0_STATUS_KUc = 1 <<  1, // Current privilege level\n\tCOP0_STATUS_IEp = 1 <<  2, // Previous interrupt enable\n\tCOP0_STATUS_KUp = 1 <<  3, // Previous privilege level\n\tCOP0_STATUS_IEo = 1 <<  4, // Old interrupt enable\n\tCOP0_STATUS_KUo = 1 <<  5, // Old privilege level\n\tCOP0_STATUS_Im0 = 1 <<  8, // IRQ mask 0 (software interrupt)\n\tCOP0_STATUS_Im1 = 1 <<  9, // IRQ mask 1 (software interrupt)\n\tCOP0_STATUS_Im2 = 1 << 10, // IRQ mask 2 (hardware interrupt)\n\tCOP0_STATUS_IsC = 1 << 16, // Isolate cache\n\tCOP0_STATUS_BEV = 1 << 22, // Boot exception vector location\n\tCOP0_STATUS_CU0 = 1 << 28, // Coprocessor 0 privilege level\n\tCOP0_STATUS_CU2 = 1 << 30  // Coprocessor 2 enable\n} COP0StatusFlag;\n\ntypedef enum {\n\tCOP0_CAUSE_EXC_BITMASK = 31 <<  2,\n\tCOP0_CAUSE_EXC_INT     =  0 <<  2, // Interrupt\n\tCOP0_CAUSE_EXC_AdEL    =  4 <<  2, // Load address error\n\tCOP0_CAUSE_EXC_AdES    =  5 <<  2, // Store address error\n\tCOP0_CAUSE_EXC_IBE     =  6 <<  2, // Instruction bus error\n\tCOP0_CAUSE_EXC_DBE     =  7 <<  2, // Data bus error\n\tCOP0_CAUSE_EXC_SYS     =  8 <<  2, // Syscall\n\tCOP0_CAUSE_EXC_BP      =  9 <<  2, // Breakpoint or break instruction\n\tCOP0_CAUSE_EXC_RI      = 10 <<  2, // Reserved instruction\n\tCOP0_CAUSE_EXC_CpU     = 11 <<  2, // Coprocessor unusable\n\tCOP0_CAUSE_EXC_Ov      = 12 <<  2, // Arithmetic overflow\n\tCOP0_CAUSE_Ip0         =  1 <<  8, // IRQ 0 pending (software interrupt)\n\tCOP0_CAUSE_Ip1         =  1 <<  9, // IRQ 1 pending (software interrupt)\n\tCOP0_CAUSE_Ip2         =  1 << 10, // IRQ 2 pending (hardware interrupt)\n\tCOP0_CAUSE_CE_BITMASK  =  3 << 28,\n\tCOP0_CAUSE_BD          =  1 << 30  // Exception occurred in delay slot\n} COP0CauseFlag;\n\n// Note that reg must be a constant value known at compile time, as the\n// mtc0/mfc0 instructions only support addressing coprocessor registers directly\n// through immediates.\nDEF(void) cop0_setReg(const COP0Register reg, uint32_t value) {\n\t__asm__ volatile(\"mtc0 %0, $%1\\n\" :: \"r\"(value), \"i\"(reg));\n}\nDEF(uint32_t) cop0_getReg(const COP0Register reg) {\n\tuint32_t value;\n\n\t__asm__ volatile(\"mfc0 %0, $%1\\n\" : \"=r\"(value) : \"i\"(reg));\n\treturn value;\n}\n\nDEF(void) cop0_enableInterrupts(void) {\n\tuint32_t status = cop0_getReg(COP0_STATUS);\n\n\tcop0_setReg(COP0_STATUS, status | COP0_STATUS_IEc);\n}\nDEF(uint32_t) cop0_disableInterrupts(void) {\n\tuint32_t status = cop0_getReg(COP0_STATUS);\n\n\tcop0_setReg(COP0_STATUS, status & ~COP0_STATUS_IEc);\n\treturn status & COP0_STATUS_IEc;\n}\n\n#undef DEF\n"
  },
  {
    "path": "src/ps1/gpucmd.h",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#pragma once\n\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdint.h>\n\n#define DEF(type) static inline type __attribute__((always_inline))\n\n/* DMA tags */\n\nDEF(uint32_t) gp0_tag(size_t length, void *next) {\n\treturn 0\n\t\t| (((uint32_t) next   & 0xffffff) <<  0)\n\t\t| (((uint32_t) length & 0x0000ff) << 24);\n}\n\nDEF(uint32_t) gp0_endTag(size_t length) {\n\treturn gp0_tag(length, (void *) 0xffffff);\n}\n\n/* Drawing attributes */\n\ntypedef enum {\n\tGP0_BLEND_BITMASK   = 3,\n\tGP0_BLEND_SEMITRANS = 0,\n\tGP0_BLEND_ADD       = 1,\n\tGP0_BLEND_SUBTRACT  = 2,\n\tGP0_BLEND_DIV4_ADD  = 3\n} GP0BlendMode;\n\ntypedef enum {\n\tGP0_COLOR_BITMASK = 3,\n\tGP0_COLOR_4BPP    = 0,\n\tGP0_COLOR_8BPP    = 1,\n\tGP0_COLOR_16BPP   = 2\n} GP0ColorDepth;\n\nDEF(uint16_t) gp0_page(\n\tunsigned int  x,\n\tunsigned int  y,\n\tGP0BlendMode  blendMode,\n\tGP0ColorDepth colorDepth\n) {\n\treturn 0\n\t\t| ((x          & 15) <<  0)\n\t\t| ((y          &  1) <<  4)\n\t\t| ((blendMode  &  3) <<  5)\n\t\t| ((colorDepth &  3) <<  7)\n\t\t| ((y          &  2) << 10);\n}\nDEF(uint16_t) gp0_clut(unsigned int x, unsigned int y) {\n\treturn 0\n\t\t| ((x & 0x03f) << 0)\n\t\t| ((y & 0x3ff) << 6);\n}\n\nDEF(uint32_t) gp0_xy(int x, int y) {\n\treturn 0\n\t\t| ((x & 0xffff) <<  0)\n\t\t| ((y & 0xffff) << 16);\n}\nDEF(uint32_t) gp0_uv(unsigned int u, unsigned int v, uint16_t attr) {\n\treturn 0\n\t\t| ((u    & 0x00ff) <<  0)\n\t\t| ((v    & 0x00ff) <<  8)\n\t\t| ((attr & 0xffff) << 16);\n}\nDEF(uint32_t) gp0_rgb(uint8_t r, uint8_t g, uint8_t b) {\n\treturn 0\n\t\t| ((r & 0xff) <<  0)\n\t\t| ((g & 0xff) <<  8)\n\t\t| ((b & 0xff) << 16);\n}\n\n/* GP0 (drawing) commands */\n\ntypedef enum {\n\tGP0_CMD_MISC       = 0 << 29,\n\tGP0_CMD_POLYGON    = 1 << 29,\n\tGP0_CMD_LINE       = 2 << 29,\n\tGP0_CMD_RECTANGLE  = 3 << 29,\n\tGP0_CMD_VRAM_BLIT  = 4 << 29,\n\tGP0_CMD_VRAM_WRITE = 5 << 29,\n\tGP0_CMD_VRAM_READ  = 6 << 29,\n\tGP0_CMD_ATTRIBUTE  = 7 << 29\n} GP0Command;\n\ntypedef enum {\n\tGP0_CMD_NOP         = GP0_CMD_MISC | ( 0 << 24),\n\tGP0_CMD_FLUSH_CACHE = GP0_CMD_MISC | ( 1 << 24),\n\tGP0_CMD_VRAM_FILL   = GP0_CMD_MISC | ( 2 << 24),\n\tGP0_CMD_NOP2        = GP0_CMD_MISC | ( 3 << 24),\n\tGP0_CMD_IRQ         = GP0_CMD_MISC | (31 << 24)\n} GP0MiscCommand;\n\ntypedef enum {\n\tGP0_CMD_TPAGE      = GP0_CMD_ATTRIBUTE | (1 << 24),\n\tGP0_CMD_TWINDOW    = GP0_CMD_ATTRIBUTE | (2 << 24),\n\tGP0_CMD_FB_OFFSET1 = GP0_CMD_ATTRIBUTE | (3 << 24),\n\tGP0_CMD_FB_OFFSET2 = GP0_CMD_ATTRIBUTE | (4 << 24),\n\tGP0_CMD_FB_ORIGIN  = GP0_CMD_ATTRIBUTE | (5 << 24),\n\tGP0_CMD_FB_MASK    = GP0_CMD_ATTRIBUTE | (6 << 24)\n} GP0AttributeCommand;\n\nDEF(uint32_t) _gp0_polygon(\n\tbool quad,\n\tbool unshaded,\n\tbool gouraud,\n\tbool textured,\n\tbool blend\n) {\n\treturn GP0_CMD_POLYGON\n\t\t| ((unshaded & 1) << 24)\n\t\t| ((blend    & 1) << 25)\n\t\t| ((textured & 1) << 26)\n\t\t| ((quad     & 1) << 27)\n\t\t| ((gouraud  & 1) << 28);\n}\nDEF(uint32_t) gp0_triangle(bool textured, bool blend) {\n\treturn _gp0_polygon(false, true, false, textured, blend);\n}\nDEF(uint32_t) gp0_shadedTriangle(bool gouraud, bool textured, bool blend) {\n\treturn _gp0_polygon(false, false, gouraud, textured, blend);\n}\nDEF(uint32_t) gp0_quad(bool textured, bool blend) {\n\treturn _gp0_polygon(true, true, false, textured, blend);\n}\nDEF(uint32_t) gp0_shadedQuad(bool gouraud, bool textured, bool blend) {\n\treturn _gp0_polygon(true, false, gouraud, textured, blend);\n}\n\nDEF(uint32_t) gp0_line(bool gouraud, bool blend) {\n\treturn GP0_CMD_LINE\n\t\t| ((blend   & 1) << 25)\n\t\t| ((gouraud & 1) << 28);\n}\nDEF(uint32_t) gp0_polyLine(bool gouraud, bool blend) {\n\treturn GP0_CMD_LINE\n\t\t| ((blend   & 1) << 25)\n\t\t| (1             << 27)\n\t\t| ((gouraud & 1) << 28);\n}\n\nDEF(uint32_t) _gp0_rectangle(\n\tuint8_t size,\n\tbool    textured,\n\tbool    unshaded,\n\tbool    blend\n) {\n\treturn GP0_CMD_RECTANGLE\n\t\t| ((unshaded & 1) << 24)\n\t\t| ((blend    & 1) << 25)\n\t\t| ((textured & 1) << 26)\n\t\t| ((size     & 3) << 27);\n}\nDEF(uint32_t) gp0_rectangle(bool textured, bool unshaded, bool blend) {\n\treturn _gp0_rectangle(0, textured, unshaded, blend);\n}\nDEF(uint32_t) gp0_rectangle1x1(bool textured, bool unshaded, bool blend) {\n\treturn _gp0_rectangle(1, textured, unshaded, blend);\n}\nDEF(uint32_t) gp0_rectangle8x8(bool textured, bool unshaded, bool blend) {\n\treturn _gp0_rectangle(2, textured, unshaded, blend);\n}\nDEF(uint32_t) gp0_rectangle16x16(bool textured, bool unshaded, bool blend) {\n\treturn _gp0_rectangle(3, textured, unshaded, blend);\n}\n\nDEF(uint32_t) gp0_vramBlit(void) {\n\treturn GP0_CMD_VRAM_BLIT;\n}\nDEF(uint32_t) gp0_vramWrite(void) {\n\treturn GP0_CMD_VRAM_WRITE;\n}\nDEF(uint32_t) gp0_vramRead(void) {\n\treturn GP0_CMD_VRAM_READ;\n}\n\nDEF(uint32_t) gp0_flushCache(void) {\n\treturn GP0_CMD_FLUSH_CACHE;\n}\nDEF(uint32_t) gp0_vramFill(void) {\n\treturn GP0_CMD_VRAM_FILL;\n}\nDEF(uint32_t) gp0_irq(void) {\n\treturn GP0_CMD_IRQ;\n}\n\nDEF(uint32_t) gp0_setPage(uint16_t page, bool dither, bool unlockFB) {\n\treturn GP0_CMD_TPAGE\n\t\t| ((page     & 0x9ff) <<  0)\n\t\t| ((dither   &     1) <<  9)\n\t\t| ((unlockFB &     1) << 10);\n}\nDEF(uint32_t) gp0_setWindow(\n\tuint8_t baseX,\n\tuint8_t baseY,\n\tuint8_t maskX,\n\tuint8_t maskY\n) {\n\treturn GP0_CMD_TWINDOW\n\t\t| ((maskX & 0x1f) <<  0)\n\t\t| ((maskY & 0x1f) <<  5)\n\t\t| ((baseX & 0x1f) << 10)\n\t\t| ((baseY & 0x1f) << 15);\n}\nDEF(uint32_t) gp0_fbOffset1(unsigned int x, unsigned int y) {\n\treturn GP0_CMD_FB_OFFSET1\n\t\t| ((x & 0x3ff) <<  0)\n\t\t| ((y & 0x3ff) << 10);\n}\nDEF(uint32_t) gp0_fbOffset2(unsigned int x, unsigned int y) {\n\treturn GP0_CMD_FB_OFFSET2\n\t\t| ((x & 0x3ff) <<  0)\n\t\t| ((y & 0x3ff) << 10);\n}\nDEF(uint32_t) gp0_fbOrigin(int x, int y) {\n\treturn GP0_CMD_FB_ORIGIN\n\t\t| ((x & 0x7ff) <<  0)\n\t\t| ((y & 0x7ff) << 11);\n}\nDEF(uint32_t) gp0_fbMask(bool setMask, bool useMask) {\n\treturn GP0_CMD_FB_MASK\n\t\t| (setMask << 0)\n\t\t| (useMask << 1);\n}\n\n/* GP1 (display control) commands */\n\ntypedef enum {\n\tGP1_HRES_BITMASK = (3 << 0) | (1 << 6),\n\tGP1_HRES_256     = 0 << 0, // Dotclock divided by 10\n\tGP1_HRES_320     = 1 << 0, // Dotclock divided by 8\n\tGP1_HRES_368     = 1 << 6, // Dotclock divided by 7\n\tGP1_HRES_512     = 2 << 0, // Dotclock divided by 5\n\tGP1_HRES_640     = 3 << 0  // Dotclock divided by 4\n} GP1HorizontalRes;\n\ntypedef enum {\n\tGP1_VRES_BITMASK = 1,\n\tGP1_VRES_256     = 0,\n\tGP1_VRES_512     = 1\n} GP1VerticalRes;\n\ntypedef enum {\n\tGP1_MODE_BITMASK = 1,\n\tGP1_MODE_NTSC    = 0,\n\tGP1_MODE_PAL     = 1\n} GP1VideoMode;\n\ntypedef enum {\n\tGP1_COLOR_BITMASK = 1,\n\tGP1_COLOR_16BPP   = 0,\n\tGP1_COLOR_24BPP   = 1\n} GP1ColorDepth;\n\ntypedef enum {\n\tGP1_DREQ_BITMASK   = 3,\n\tGP1_DREQ_NONE      = 0,\n\tGP1_DREQ_FIFO      = 1,\n\tGP1_DREQ_GP0_WRITE = 2,\n\tGP1_DREQ_GP0_READ  = 3\n} GP1DMARequestMode;\n\ntypedef enum {\n\tGP1_VRAM_BITMASK = 1,\n\tGP1_VRAM_1MB     = 0,\n\tGP1_VRAM_2MB     = 1\n} GP1VRAMSize;\n\ntypedef enum {\n\tGP1_CMD_RESET_GPU   =  0 << 24,\n\tGP1_CMD_RESET_FIFO  =  1 << 24,\n\tGP1_CMD_ACKNOWLEDGE =  2 << 24,\n\tGP1_CMD_DISP_BLANK  =  3 << 24,\n\tGP1_CMD_DREQ_MODE   =  4 << 24,\n\tGP1_CMD_FB_OFFSET   =  5 << 24,\n\tGP1_CMD_FB_RANGE_H  =  6 << 24,\n\tGP1_CMD_FB_RANGE_V  =  7 << 24,\n\tGP1_CMD_FB_MODE     =  8 << 24,\n\tGP1_CMD_VRAM_SIZE   =  9 << 24,\n\tGP1_CMD_GET_INFO    = 16 << 24\n} GP1Command;\n\nDEF(uint32_t) gp1_clockMultiplierH(GP1HorizontalRes horizontalRes) {\n\tswitch (horizontalRes) {\n\t\tcase GP1_HRES_256:\n\t\t\treturn 10;\n\t\tcase GP1_HRES_320:\n\t\t\treturn 8;\n\t\tcase GP1_HRES_368:\n\t\t\treturn 7;\n\t\tcase GP1_HRES_512:\n\t\t\treturn 5;\n\t\tcase GP1_HRES_640:\n\t\t\treturn 4;\n\t\tdefault:\n\t\t\treturn 0;\n\t}\n}\n\nDEF(uint32_t) gp1_clockDividerV(GP1VerticalRes verticalRes) {\n\tswitch (verticalRes) {\n\t\tcase GP1_VRES_256:\n\t\t\treturn 1;\n\t\tcase GP1_VRES_512:\n\t\t\treturn 2;\n\t\tdefault:\n\t\t\treturn 0;\n\t}\n}\n\nDEF(uint32_t) gp1_resetGPU(void) {\n\treturn GP1_CMD_RESET_GPU;\n}\nDEF(uint32_t) gp1_resetFIFO(void) {\n\treturn GP1_CMD_RESET_FIFO;\n}\nDEF(uint32_t) gp1_acknowledge(void) {\n\treturn GP1_CMD_ACKNOWLEDGE;\n}\nDEF(uint32_t) gp1_dispBlank(bool blank) {\n\treturn GP1_CMD_DISP_BLANK\n\t\t| (blank & 1);\n}\nDEF(uint32_t) gp1_dmaRequestMode(GP1DMARequestMode mode) {\n\treturn GP1_CMD_DREQ_MODE\n\t\t| (mode & 3);\n}\nDEF(uint32_t) gp1_fbOffset(unsigned int x, unsigned int y) {\n\treturn GP1_CMD_FB_OFFSET\n\t\t| ((x & 0x3ff) <<  0)\n\t\t| ((y & 0x3ff) << 10);\n}\nDEF(uint32_t) gp1_fbRangeH(unsigned int low, unsigned int high) {\n\treturn GP1_CMD_FB_RANGE_H\n\t\t| ((low  & 0xfff) <<  0)\n\t\t| ((high & 0xfff) << 12);\n}\nDEF(uint32_t) gp1_fbRangeV(unsigned int low, unsigned int high) {\n\treturn GP1_CMD_FB_RANGE_V\n\t\t| ((low  & 0x3ff) <<  0)\n\t\t| ((high & 0x3ff) << 10);\n}\nDEF(uint32_t) gp1_fbMode(\n\tGP1HorizontalRes horizontalRes,\n\tGP1VerticalRes   verticalRes,\n\tGP1VideoMode     videoMode,\n\tbool             interlace,\n\tGP1ColorDepth    colorDepth\n) {\n\treturn GP1_CMD_FB_MODE\n\t\t| ((horizontalRes & 0x47) << 0)\n\t\t| ((verticalRes   &    1) << 2)\n\t\t| ((videoMode     &    1) << 3)\n\t\t| ((colorDepth    &    1) << 4)\n\t\t| ((interlace     &    1) << 5);\n}\nDEF(uint32_t) gp1_vramSize(GP1VRAMSize size) {\n\treturn GP1_CMD_VRAM_SIZE\n\t\t| (size & 1);\n}\n\n#undef DEF\n"
  },
  {
    "path": "src/ps1/gte.h",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#pragma once\n\n#include <stdint.h>\n\n#define DEF(type) static inline type __attribute__((always_inline))\n\n/* GTE data types */\n\n// The GTE stores 16-bit vectors and matrices in 32-bit registers, packing two\n// values into each register. Since lwc2/swc2 can only perform aligned memory\n// accesses (see gte_loadDataReg() and gte_storeDataReg()), the structures below\n// must always be aligned to 32 bits.\ntypedef struct __attribute__((aligned(4))) {\n\tint16_t x, y;\n\tint16_t z, _padding;\n} GTEVector16;\n\ntypedef struct __attribute__((aligned(4))) {\n\tint32_t x, y, z;\n} GTEVector32;\n\ntypedef struct __attribute__((aligned(4))) {\n\tint16_t values[3][3];\n\tint16_t _padding;\n} GTEMatrix;\n\n/* Command definitions */\n\ntypedef enum {\n\tGTE_CMD_BITMASK = 63 <<  0,\n\tGTE_CMD_RTPS    =  1 <<  0, // Perspective transformation (1 vertex)\n\tGTE_CMD_NCLIP   =  6 <<  0, // Normal clipping\n\tGTE_CMD_OP      = 12 <<  0, // Outer product\n\tGTE_CMD_DPCS    = 16 <<  0, // Depth cue (1 vertex)\n\tGTE_CMD_INTPL   = 17 <<  0, // Depth cue with vector\n\tGTE_CMD_MVMVA   = 18 <<  0, // Matrix-vector multiplication\n\tGTE_CMD_NCDS    = 19 <<  0, // Normal color depth (1 vertex)\n\tGTE_CMD_CDP     = 20 <<  0, // Color depth cue\n\tGTE_CMD_NCDT    = 22 <<  0, // Normal color depth (3 vertices)\n\tGTE_CMD_NCCS    = 27 <<  0, // Normal color color (1 vertex)\n\tGTE_CMD_CC      = 28 <<  0, // Color color\n\tGTE_CMD_NCS     = 30 <<  0, // Normal color (1 vertex)\n\tGTE_CMD_NCT     = 32 <<  0, // Normal color (3 vertices)\n\tGTE_CMD_SQR     = 40 <<  0, // Square of vector\n\tGTE_CMD_DCPL    = 41 <<  0, // Depth cue with light\n\tGTE_CMD_DPCT    = 42 <<  0, // Depth cue (3 vertices)\n\tGTE_CMD_AVSZ3   = 45 <<  0, // Average Z value (3 vertices)\n\tGTE_CMD_AVSZ4   = 46 <<  0, // Average Z value (4 vertices)\n\tGTE_CMD_RTPT    = 48 <<  0, // Perspective transformation (3 vertices)\n\tGTE_CMD_GPF     = 61 <<  0, // Linear interpolation\n\tGTE_CMD_GPL     = 62 <<  0, // Linear interpolation with base\n\tGTE_CMD_NCCT    = 63 <<  0, // Normal color color (3 vertices)\n\tGTE_LM          =  1 << 10, // Saturate IR to 0x0000-0x7fff\n\tGTE_CV_BITMASK  =  3 << 13,\n\tGTE_CV_TR       =  0 << 13, // Use TR as translation vector for MVMVA\n\tGTE_CV_BK       =  1 << 13, // Use BK as translation vector for MVMVA\n\tGTE_CV_FC       =  2 << 13, // Use FC as translation vector for MVMVA\n\tGTE_CV_NONE     =  3 << 13, // Skip translation for MVMVA\n\tGTE_V_BITMASK   =  3 << 15,\n\tGTE_V_V0        =  0 << 15, // Use V0 as operand for MVMVA\n\tGTE_V_V1        =  1 << 15, // Use V1 as operand for MVMVA\n\tGTE_V_V2        =  2 << 15, // Use V2 as operand for MVMVA\n\tGTE_V_IR        =  3 << 15, // Use IR as operand for MVMVA\n\tGTE_MX_BITMASK  =  3 << 17,\n\tGTE_MX_RT       =  0 << 17, // Use rotation matrix as operand for MVMVA\n\tGTE_MX_LLM      =  1 << 17, // Use light matrix as operand for MVMVA\n\tGTE_MX_LCM      =  2 << 17, // Use light color matrix as operand for MVMVA\n\tGTE_SF          =  1 << 19  // Shift results by 12 bits\n} GTECommandFlag;\n\nDEF(void) gte_command(const uint32_t cmd) {\n\t__asm__ volatile(\n\t\t\"nop\\n\"\n\t\t\"nop\\n\"\n\t\t\"cop2 %0\\n\"\n\t\t:: \"i\"(cmd)\n\t);\n}\n\n/* Control register definitions */\n\ntypedef enum {\n\tGTE_RT11RT12 =  0, // Rotation matrix\n\tGTE_RT13RT21 =  1, // Rotation matrix\n\tGTE_RT22RT23 =  2, // Rotation matrix\n\tGTE_RT31RT32 =  3, // Rotation matrix\n\tGTE_RT33     =  4, // Rotation matrix\n\tGTE_TRX      =  5, // Translation vector\n\tGTE_TRY      =  6, // Translation vector\n\tGTE_TRZ      =  7, // Translation vector\n\tGTE_L11L12   =  8, // Light matrix\n\tGTE_L13L21   =  9, // Light matrix\n\tGTE_L22L23   = 10, // Light matrix\n\tGTE_L31L32   = 11, // Light matrix\n\tGTE_L33      = 12, // Light matrix\n\tGTE_RBK      = 13, // Background color\n\tGTE_GBK      = 14, // Background color\n\tGTE_BBK      = 15, // Background color\n\tGTE_LC11LC12 = 16, // Light color matrix\n\tGTE_LC13LC21 = 17, // Light color matrix\n\tGTE_LC22LC23 = 18, // Light color matrix\n\tGTE_LC31LC32 = 19, // Light color matrix\n\tGTE_LC33     = 20, // Light color matrix\n\tGTE_RFC      = 21, // Far color\n\tGTE_GFC      = 22, // Far color\n\tGTE_BFC      = 23, // Far color\n\tGTE_OFX      = 24, // Screen coordinate offset\n\tGTE_OFY      = 25, // Screen coordinate offset\n\tGTE_H        = 26, // Projection plane distance\n\tGTE_DQA      = 27, // Depth cue scale factor\n\tGTE_DQB      = 28, // Depth cue base\n\tGTE_ZSF3     = 29, // Average Z scale factor\n\tGTE_ZSF4     = 30, // Average Z scale factor\n\tGTE_FLAG     = 31  // Error/overflow flags\n} GTEControlRegister;\n\ntypedef enum {\n\tGTE_FLAG_IR0_SATURATED   = 1 << 12,\n\tGTE_FLAG_SY2_SATURATED   = 1 << 13,\n\tGTE_FLAG_SX2_SATURATED   = 1 << 14,\n\tGTE_FLAG_MAC0_UNDERFLOW  = 1 << 15,\n\tGTE_FLAG_MAC0_OVERFLOW   = 1 << 16,\n\tGTE_FLAG_DIVIDE_OVERFLOW = 1 << 17,\n\tGTE_FLAG_Z_SATURATED     = 1 << 18,\n\tGTE_FLAG_B_SATURATED     = 1 << 19,\n\tGTE_FLAG_G_SATURATED     = 1 << 20,\n\tGTE_FLAG_R_SATURATED     = 1 << 21,\n\tGTE_FLAG_IR3_SATURATED   = 1 << 22,\n\tGTE_FLAG_IR2_SATURATED   = 1 << 23,\n\tGTE_FLAG_IR1_SATURATED   = 1 << 24,\n\tGTE_FLAG_MAC3_UNDERFLOW  = 1 << 25,\n\tGTE_FLAG_MAC2_UNDERFLOW  = 1 << 26,\n\tGTE_FLAG_MAC1_UNDERFLOW  = 1 << 27,\n\tGTE_FLAG_MAC3_OVERFLOW   = 1 << 28,\n\tGTE_FLAG_MAC2_OVERFLOW   = 1 << 29,\n\tGTE_FLAG_MAC1_OVERFLOW   = 1 << 30,\n\tGTE_FLAG_ERROR           = 1 << 31\n} GTEStatusFlag;\n\n// Note that reg must be a constant value known at compile time, as the\n// cfc2/ctc2 instructions only support addressing coprocessor registers directly\n// through immediates.\nDEF(void) gte_setControlReg(const GTEControlRegister reg, uint32_t value) {\n\t__asm__ volatile(\"ctc2 %0, $%1\\n\" :: \"r\"(value), \"i\"(reg));\n}\nDEF(uint32_t) gte_getControlReg(const GTEControlRegister reg) {\n\tuint32_t value;\n\n\t__asm__ volatile(\"cfc2 %0, $%1\\n\" : \"=r\"(value) : \"i\"(reg));\n\treturn value;\n}\n\n#define MATRIX_FUNCTIONS(reg0, reg1, reg2, reg3, reg4, name) \\\n\tDEF(void) gte_set##name( \\\n\t\tint16_t v11, int16_t v12, int16_t v13, \\\n\t\tint16_t v21, int16_t v22, int16_t v23, \\\n\t\tint16_t v31, int16_t v32, int16_t v33 \\\n\t) { \\\n\t\tgte_setControlReg(reg0, ((uint32_t) v11 & 0xffff) | ((uint32_t) v12 << 16)); \\\n\t\tgte_setControlReg(reg1, ((uint32_t) v13 & 0xffff) | ((uint32_t) v21 << 16)); \\\n\t\tgte_setControlReg(reg2, ((uint32_t) v22 & 0xffff) | ((uint32_t) v23 << 16)); \\\n\t\tgte_setControlReg(reg3, ((uint32_t) v31 & 0xffff) | ((uint32_t) v32 << 16)); \\\n\t\tgte_setControlReg(reg4, v33); \\\n\t} \\\n\tDEF(void) gte_load##name(const GTEMatrix *input) { \\\n\t\tconst uint32_t *values = (const uint32_t *) input; \\\n\t\t\\\n\t\tgte_setControlReg(reg0, values[0]); \\\n\t\tgte_setControlReg(reg1, values[1]); \\\n\t\tgte_setControlReg(reg2, values[2]); \\\n\t\tgte_setControlReg(reg3, values[3]); \\\n\t\tgte_setControlReg(reg4, values[4]); \\\n\t} \\\n\tDEF(void) gte_store##name(GTEMatrix *output) { \\\n\t\tuint32_t *values = (uint32_t *) output; \\\n\t\t\\\n\t\tvalues[0] = gte_getControlReg(reg0); \\\n\t\tvalues[1] = gte_getControlReg(reg1); \\\n\t\tvalues[2] = gte_getControlReg(reg2); \\\n\t\tvalues[3] = gte_getControlReg(reg3); \\\n\t\tvalues[4] = gte_getControlReg(reg4); \\\n\t}\n\nMATRIX_FUNCTIONS(\n\tGTE_RT11RT12,\n\tGTE_RT13RT21,\n\tGTE_RT22RT23,\n\tGTE_RT31RT32,\n\tGTE_RT33,\n\tRotationMatrix\n)\nMATRIX_FUNCTIONS(\n\tGTE_L11L12,\n\tGTE_L13L21,\n\tGTE_L22L23,\n\tGTE_L31L32,\n\tGTE_L33,\n\tLightMatrix\n)\nMATRIX_FUNCTIONS(\n\tGTE_LC11LC12,\n\tGTE_LC13LC21,\n\tGTE_LC22LC23,\n\tGTE_LC31LC32,\n\tGTE_LC33,\n\tLightColorMatrix\n)\n\n#undef MATRIX_FUNCTIONS\n\n/* Data register definitions */\n\ntypedef enum {\n\tGTE_VXY0 =  0, // Input vector 0\n\tGTE_VZ0  =  1, // Input vector 0\n\tGTE_VXY1 =  2, // Input vector 1\n\tGTE_VZ1  =  3, // Input vector 1\n\tGTE_VXY2 =  4, // Input vector 2\n\tGTE_VZ2  =  5, // Input vector 2\n\tGTE_RGBC =  6, // Input color and GPU command\n\tGTE_OTZ  =  7, // Average Z value output\n\tGTE_IR0  =  8, // Scalar accumulator\n\tGTE_IR1  =  9, // Vector accumulator\n\tGTE_IR2  = 10, // Vector accumulator\n\tGTE_IR3  = 11, // Vector accumulator\n\tGTE_SXY0 = 12, // X/Y coordinate output FIFO\n\tGTE_SXY1 = 13, // X/Y coordinate output FIFO\n\tGTE_SXY2 = 14, // X/Y coordinate output FIFO\n\tGTE_SXYP = 15, // X/Y coordinate output FIFO\n\tGTE_SZ0  = 16, // Z coordinate output FIFO\n\tGTE_SZ1  = 17, // Z coordinate output FIFO\n\tGTE_SZ2  = 18, // Z coordinate output FIFO\n\tGTE_SZ3  = 19, // Z coordinate output FIFO\n\tGTE_RGB0 = 20, // Color and GPU command output FIFO\n\tGTE_RGB1 = 21, // Color and GPU command output FIFO\n\tGTE_RGB2 = 22, // Color and GPU command output FIFO\n\tGTE_MAC0 = 24, // Extended scalar accumulator\n\tGTE_MAC1 = 25, // Extended vector accumulator\n\tGTE_MAC2 = 26, // Extended vector accumulator\n\tGTE_MAC3 = 27, // Extended vector accumulator\n\tGTE_IRGB = 28, // RGB conversion input\n\tGTE_ORGB = 29, // RGB conversion output\n\tGTE_LZCS = 30, // Leading zero count input\n\tGTE_LZCR = 31  // Leading zero count output\n} GTEDataRegister;\n\nDEF(void) gte_setDataReg(const GTEDataRegister reg, uint32_t value) {\n\t__asm__ volatile(\"mtc2 %0, $%1\\n\" :: \"r\"(value), \"i\"(reg));\n}\nDEF(uint32_t) gte_getDataReg(const GTEDataRegister reg) {\n\tuint32_t value;\n\n\t__asm__ volatile(\"mfc2 %0, $%1\\n\" : \"=r\"(value) : \"i\"(reg));\n\treturn value;\n}\n\n// Unlike COP0 registers and GTE control registers, whose contents can only be\n// moved to/from a CPU register, data registers can additionally be loaded from\n// and stored to memory directly using the lwc2 and swc2 instructions.\nDEF(void) gte_loadDataReg(\n\tconst GTEDataRegister reg,\n\tconst int16_t         offset,\n\tconst void            *ptr\n) {\n\t__asm__ volatile(\"lwc2 $%0, %1(%2)\\n\" :: \"i\"(reg), \"i\"(offset), \"r\"(ptr));\n}\nDEF(void) gte_storeDataReg(\n\tconst GTEDataRegister reg,\n\tconst int16_t         offset,\n\tvoid                  *ptr\n) {\n\t__asm__ volatile(\"swc2 $%0, %1(%2)\\n\" :: \"i\"(reg), \"i\"(offset), \"r\"(ptr) : \"memory\");\n}\n\n#define VECTOR_FUNCTIONS(reg0, reg1, name) \\\n\tDEF(void) gte_set##name(int16_t x, int16_t y, int16_t z) { \\\n\t\tgte_setDataReg(reg0, ((uint32_t) x & 0xffff) | ((uint32_t) y << 16)); \\\n\t\tgte_setDataReg(reg1, z); \\\n\t} \\\n\tDEF(void) gte_load##name(const GTEVector16 *input) { \\\n\t\tgte_loadDataReg(reg0, 0, input); \\\n\t\tgte_loadDataReg(reg1, 4, input); \\\n\t} \\\n\tDEF(void) gte_store##name(GTEVector16 *output) { \\\n\t\tgte_storeDataReg(reg0, 0, output); \\\n\t\tgte_storeDataReg(reg1, 4, output); \\\n\t}\n\nVECTOR_FUNCTIONS(GTE_VXY0, GTE_VZ0, V0)\nVECTOR_FUNCTIONS(GTE_VXY1, GTE_VZ1, V1)\nVECTOR_FUNCTIONS(GTE_VXY2, GTE_VZ2, V2)\n\n#undef VECTOR_FUNCTIONS\n\nDEF(void) gte_setRowVectors(\n\tint16_t v11, int16_t v12, int16_t v13,\n\tint16_t v21, int16_t v22, int16_t v23,\n\tint16_t v31, int16_t v32, int16_t v33\n) {\n\tgte_setDataReg(GTE_VXY0, ((uint32_t) v11 & 0xffff) | ((uint32_t) v12 << 16));\n\tgte_setDataReg(GTE_VZ0,  v13);\n\tgte_setDataReg(GTE_VXY1, ((uint32_t) v21 & 0xffff) | ((uint32_t) v22 << 16));\n\tgte_setDataReg(GTE_VZ1,  v23);\n\tgte_setDataReg(GTE_VXY2, ((uint32_t) v31 & 0xffff) | ((uint32_t) v32 << 16));\n\tgte_setDataReg(GTE_VZ2,  v33);\n}\nDEF(void) gte_setColumnVectors(\n\tint16_t v11, int16_t v12, int16_t v13,\n\tint16_t v21, int16_t v22, int16_t v23,\n\tint16_t v31, int16_t v32, int16_t v33\n) {\n\tgte_setDataReg(GTE_VXY0, ((uint32_t) v11 & 0xffff) | ((uint32_t) v21 << 16));\n\tgte_setDataReg(GTE_VZ0,  v31);\n\tgte_setDataReg(GTE_VXY1, ((uint32_t) v12 & 0xffff) | ((uint32_t) v22 << 16));\n\tgte_setDataReg(GTE_VZ1,  v32);\n\tgte_setDataReg(GTE_VXY2, ((uint32_t) v13 & 0xffff) | ((uint32_t) v23 << 16));\n\tgte_setDataReg(GTE_VZ2,  v33);\n}\n\n#undef DEF\n"
  },
  {
    "path": "src/ps1/registers.h",
    "content": "/*\n * ps1-bare-metal - (C) 2023-2025 spicyjpeg\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n#pragma once\n\n#include <stdint.h>\n\n#define _ADDR8(addr)  ((volatile uint8_t *) (addr))\n#define _ADDR16(addr) ((volatile uint16_t *) (addr))\n#define _ADDR32(addr) ((volatile uint32_t *) (addr))\n#define _MMIO8(addr)  (*_ADDR8(addr))\n#define _MMIO16(addr) (*_ADDR16(addr))\n#define _MMIO32(addr) (*_ADDR32(addr))\n\n/* Constants */\n\n#define F_CPU      33868800\n#define F_GPU_NTSC 53693175\n#define F_GPU_PAL  53203425\n\ntypedef enum {\n\tDEV0_BASE  = 0xbf000000,\n\tCACHE_BASE = 0x9f800000, // Cannot be accessed from KSEG1\n\tIO_BASE    = 0xbf801000,\n\tDEV8_BASE  = 0xbf802000,\n\tDEV1_BASE  = 0xbfa00000,\n\tDEV2_BASE  = 0xbfc00000,\n\tCPU_BASE   = 0xfffe0000\n} BaseAddress;\n\n/* Bus interface */\n\ntypedef enum {\n\tBIU_CTRL_WRITE_DELAY_BITMASK = 15 <<  0,\n\tBIU_CTRL_READ_DELAY_BITMASK  = 15 <<  4,\n\tBIU_CTRL_RECOVERY            =  1 <<  8,\n\tBIU_CTRL_HOLD                =  1 <<  9,\n\tBIU_CTRL_FLOAT               =  1 << 10,\n\tBIU_CTRL_PRESTROBE           =  1 << 11,\n\tBIU_CTRL_WIDTH_8             =  0 << 12,\n\tBIU_CTRL_WIDTH_16            =  1 << 12,\n\tBIU_CTRL_AUTO_INCR           =  1 << 13,\n\tBIU_CTRL_SIZE_BITMASK        = 31 << 16,\n\tBIU_CTRL_DMA_DELAY_BITMASK   = 15 << 24,\n\tBIU_CTRL_ADDR_ERROR          =  1 << 28,\n\tBIU_CTRL_DMA_DELAY           =  1 << 29,\n\tBIU_CTRL_DMA32               =  1 << 30,\n\tBIU_CTRL_WAIT                =  1 << 31\n} BIUControlFlag;\n\n#define BIU_DEV0_ADDR _MMIO32(IO_BASE | 0x000) // PIO/arcade\n#define BIU_DEV8_ADDR _MMIO32(IO_BASE | 0x004) // PIO/debug\n#define BIU_DEV0_CTRL _MMIO32(IO_BASE | 0x008) // PIO/arcade\n#define BIU_DEV1_CTRL _MMIO32(IO_BASE | 0x00c) // PIO/arcade/debug\n#define BIU_DEV2_CTRL _MMIO32(IO_BASE | 0x010) // BIOS ROM\n#define BIU_DEV4_CTRL _MMIO32(IO_BASE | 0x014) // SPU\n#define BIU_DEV5_CTRL _MMIO32(IO_BASE | 0x018) // CD-ROM\n#define BIU_DEV8_CTRL _MMIO32(IO_BASE | 0x01c) // PIO/debug\n#define BIU_COM_DELAY _MMIO32(IO_BASE | 0x020)\n\n/* Serial interfaces */\n\ntypedef enum {\n\tSIO_STAT_TX_NOT_FULL   = 1 << 0,\n\tSIO_STAT_RX_NOT_EMPTY  = 1 << 1,\n\tSIO_STAT_TX_EMPTY      = 1 << 2,\n\tSIO_STAT_RX_PARITY_ERR = 1 << 3,\n\tSIO_STAT_RX_OVERRUN    = 1 << 4, // SIO1 only\n\tSIO_STAT_RX_STOP_ERR   = 1 << 5, // SIO1 only\n\tSIO_STAT_RX_INVERT     = 1 << 6, // SIO1 only\n\tSIO_STAT_DSR           = 1 << 7, // DSR is /ACK on SIO0\n\tSIO_STAT_CTS           = 1 << 8, // SIO1 only\n\tSIO_STAT_IRQ           = 1 << 9\n} SIOStatusFlag;\n\ntypedef enum {\n\tSIO_MODE_BAUD_BITMASK   = 3 << 0,\n\tSIO_MODE_BAUD_DIV1      = 1 << 0,\n\tSIO_MODE_BAUD_DIV16     = 2 << 0,\n\tSIO_MODE_BAUD_DIV64     = 3 << 0,\n\tSIO_MODE_DATA_BITMASK   = 3 << 2,\n\tSIO_MODE_DATA_5         = 0 << 2,\n\tSIO_MODE_DATA_6         = 1 << 2,\n\tSIO_MODE_DATA_7         = 2 << 2,\n\tSIO_MODE_DATA_8         = 3 << 2,\n\tSIO_MODE_PARITY_BITMASK = 3 << 4,\n\tSIO_MODE_PARITY_NONE    = 0 << 4,\n\tSIO_MODE_PARITY_EVEN    = 1 << 4,\n\tSIO_MODE_PARITY_ODD     = 3 << 4,\n\tSIO_MODE_STOP_BITMASK   = 3 << 6, // SIO1 only\n\tSIO_MODE_STOP_1         = 1 << 6, // SIO1 only\n\tSIO_MODE_STOP_1_5       = 2 << 6, // SIO1 only\n\tSIO_MODE_STOP_2         = 3 << 6, // SIO1 only\n\tSIO_MODE_SCK_INVERT     = 1 << 8  // SIO0 only\n} SIOModeFlag;\n\ntypedef enum {\n\tSIO_CTRL_TX_ENABLE      = 1 <<  0,\n\tSIO_CTRL_DTR            = 1 <<  1, // DTR is /CS on SIO0\n\tSIO_CTRL_RX_ENABLE      = 1 <<  2,\n\tSIO_CTRL_TX_INVERT      = 1 <<  3, // SIO1 only\n\tSIO_CTRL_ACKNOWLEDGE    = 1 <<  4,\n\tSIO_CTRL_RTS            = 1 <<  5, // SIO1 only\n\tSIO_CTRL_RESET          = 1 <<  6,\n\tSIO_CTRL_TX_IRQ_ENABLE  = 1 << 10,\n\tSIO_CTRL_RX_IRQ_ENABLE  = 1 << 11,\n\tSIO_CTRL_DSR_IRQ_ENABLE = 1 << 12, // DSR is /ACK on SIO0\n\tSIO_CTRL_CS_PORT_1      = 0 << 13, // SIO0 only\n\tSIO_CTRL_CS_PORT_2      = 1 << 13  // SIO0 only\n} SIOControlFlag;\n\n// SIO_DATA is a 32-bit register, but some emulators do not implement it\n// correctly and break if it's read more than 8 bits at a time.\n#define SIO_DATA(N) _MMIO8 ((IO_BASE | 0x040) + (16 * (N)))\n#define SIO_STAT(N) _MMIO16((IO_BASE | 0x044) + (16 * (N)))\n#define SIO_MODE(N) _MMIO16((IO_BASE | 0x048) + (16 * (N)))\n#define SIO_CTRL(N) _MMIO16((IO_BASE | 0x04a) + (16 * (N)))\n#define SIO_BAUD(N) _MMIO16((IO_BASE | 0x04e) + (16 * (N)))\n\n/* DRAM controller */\n\ntypedef enum {\n\tDRAM_CTRL_UNKNOWN1        = 1 <<  3,\n\tDRAM_CTRL_REFRESH_BITMASK = 3 <<  4,\n\tDRAM_CTRL_REFRESH_256     = 0 <<  4,\n\tDRAM_CTRL_REFRESH_320     = 1 <<  4,\n\tDRAM_CTRL_REFRESH_384     = 2 <<  4,\n\tDRAM_CTRL_REFRESH_448     = 3 <<  4,\n\tDRAM_CTRL_FETCH_DELAY     = 1 <<  7,\n\tDRAM_CTRL_UNKNOWN2        = 1 <<  8,\n\tDRAM_CTRL_SIZE_BITMASK    = (1 << 9) | (1 << 11),\n\tDRAM_CTRL_SIZE_1MB        = (0 << 9) | (0 << 11),\n\tDRAM_CTRL_SIZE_2MB        = (0 << 9) | (1 << 11),\n\tDRAM_CTRL_SIZE_4MB        = (1 << 9) | (0 << 11),\n\tDRAM_CTRL_SIZE_8MB        = (1 << 9) | (1 << 11),\n\tDRAM_CTRL_BANKS_BITMASK   = 0 << 10,\n\tDRAM_CTRL_BANKS_1         = 0 << 10,\n\tDRAM_CTRL_BANKS_2         = 1 << 10\n} DRAMControlFlag;\n\n#define DRAM_CTRL _MMIO32(IO_BASE | 0x060)\n\n/* IRQ controller */\n\ntypedef enum {\n\tIRQ_VSYNC  =  0,\n\tIRQ_GPU    =  1,\n\tIRQ_CDROM  =  2,\n\tIRQ_DMA    =  3,\n\tIRQ_TIMER0 =  4,\n\tIRQ_TIMER1 =  5,\n\tIRQ_TIMER2 =  6,\n\tIRQ_SIO0   =  7,\n\tIRQ_SIO1   =  8,\n\tIRQ_SPU    =  9,\n\tIRQ_GUN    = 10,\n\tIRQ_PIO    = 10\n} IRQChannel;\n\n#define IRQ_STAT _MMIO16(IO_BASE | 0x070)\n#define IRQ_MASK _MMIO16(IO_BASE | 0x074)\n\n/* DMA */\n\ntypedef enum {\n\tDMA_MDEC_IN  = 0,\n\tDMA_MDEC_OUT = 1,\n\tDMA_GPU      = 2,\n\tDMA_CDROM    = 3,\n\tDMA_SPU      = 4,\n\tDMA_PIO      = 5,\n\tDMA_OTC      = 6\n} DMAChannel;\n\ntypedef enum {\n\tDMA_CHCR_READ             = 0 <<  0,\n\tDMA_CHCR_WRITE            = 1 <<  0,\n\tDMA_CHCR_REVERSE          = 1 <<  1,\n\tDMA_CHCR_CHOPPING         = 1 <<  8,\n\tDMA_CHCR_MODE_BITMASK     = 3 <<  9,\n\tDMA_CHCR_MODE_BURST       = 0 <<  9,\n\tDMA_CHCR_MODE_SLICE       = 1 <<  9,\n\tDMA_CHCR_MODE_LIST        = 2 <<  9,\n\tDMA_CHCR_DMA_TIME_BITMASK = 7 << 16,\n\tDMA_CHCR_CPU_TIME_BITMASK = 7 << 20,\n\tDMA_CHCR_ENABLE           = 1 << 24,\n\tDMA_CHCR_TRIGGER          = 1 << 28,\n\tDMA_CHCR_PAUSE            = 1 << 29  // Burst mode only\n} DMACHCRFlag;\n\n#define DMA_DPCR_CH_PRIORITY_BITMASK(N)   (7              << (4 * (N)))\n#define DMA_DPCR_CH_PRIORITY(N, priority) ((priority & 7) << (4 * (N)))\n#define DMA_DPCR_CH_ENABLE(N)             ((1 << 3)       << (4 * (N)))\n\ntypedef enum {\n\tDMA_DICR_CH_MODE_BITMASK   = 0x7f <<  0,\n\tDMA_DICR_BUS_ERROR         =    1 << 15,\n\tDMA_DICR_CH_ENABLE_BITMASK = 0x7f << 16,\n\tDMA_DICR_IRQ_ENABLE        =    1 << 23,\n\tDMA_DICR_CH_STAT_BITMASK   = 0x7f << 24,\n\tDMA_DICR_IRQ               =    1 << 31\n} DMADICRFlag;\n\n#define DMA_DICR_CH_MODE(N)   (1 << ((N) +  0))\n#define DMA_DICR_CH_ENABLE(N) (1 << ((N) + 16))\n#define DMA_DICR_CH_STAT(N)   (1 << ((N) + 24))\n\n#define DMA_MADR(N) _MMIO32((IO_BASE | 0x080) + (16 * (N)))\n#define DMA_BCR(N)  _MMIO32((IO_BASE | 0x084) + (16 * (N)))\n#define DMA_CHCR(N) _MMIO32((IO_BASE | 0x088) + (16 * (N)))\n\n#define DMA_DPCR _MMIO32(IO_BASE | 0x0f0)\n#define DMA_DICR _MMIO32(IO_BASE | 0x0f4)\n\n/* Timers */\n\ntypedef enum {\n\tTIMER_CTRL_ENABLE_SYNC     = 1 <<  0,\n\tTIMER_CTRL_SYNC_BITMASK    = 3 <<  1,\n\tTIMER_CTRL_SYNC_PAUSE      = 0 <<  1,\n\tTIMER_CTRL_SYNC_RESET1     = 1 <<  1,\n\tTIMER_CTRL_SYNC_RESET2     = 2 <<  1,\n\tTIMER_CTRL_SYNC_PAUSE_ONCE = 3 <<  1,\n\tTIMER_CTRL_RELOAD          = 1 <<  3,\n\tTIMER_CTRL_IRQ_ON_RELOAD   = 1 <<  4,\n\tTIMER_CTRL_IRQ_ON_OVERFLOW = 1 <<  5,\n\tTIMER_CTRL_IRQ_REPEAT      = 1 <<  6,\n\tTIMER_CTRL_IRQ_LATCH       = 1 <<  7,\n\tTIMER_CTRL_EXT_CLOCK       = 1 <<  8,\n\tTIMER_CTRL_PRESCALE        = 1 <<  9,\n\tTIMER_CTRL_IRQ             = 1 << 10,\n\tTIMER_CTRL_RELOADED        = 1 << 11,\n\tTIMER_CTRL_OVERFLOWED      = 1 << 12\n} TimerControlFlag;\n\n#define TIMER_VALUE(N)  _MMIO16((IO_BASE | 0x100) + (16 * (N)))\n#define TIMER_CTRL(N)   _MMIO16((IO_BASE | 0x104) + (16 * (N)))\n#define TIMER_RELOAD(N) _MMIO16((IO_BASE | 0x108) + (16 * (N)))\n\n/* CD-ROM drive */\n\ntypedef enum {\n\tCDROM_HSTS_RA_BITMASK = 3 << 0,\n\tCDROM_HSTS_ADPBUSY    = 1 << 2,\n\tCDROM_HSTS_PRMEMPT    = 1 << 3,\n\tCDROM_HSTS_PRMWRDY    = 1 << 4,\n\tCDROM_HSTS_RSLRRDY    = 1 << 5,\n\tCDROM_HSTS_DRQSTS     = 1 << 6,\n\tCDROM_HSTS_BUSYSTS    = 1 << 7\n} CDROMHSTSFlag;\n\ntypedef enum {\n\tCDROM_HINT_INT_BITMASK = 7 << 0,\n\tCDROM_HINT_INT0        = 1 << 0,\n\tCDROM_HINT_INT1        = 1 << 1,\n\tCDROM_HINT_INT2        = 1 << 2,\n\tCDROM_HINT_BFEMPT      = 1 << 3,\n\tCDROM_HINT_BFWRDY      = 1 << 4\n} CDROMHINTFlag;\n\ntypedef enum {\n\tCDROM_HCHPCTL_SMEN = 1 << 5,\n\tCDROM_HCHPCTL_BFWR = 1 << 6,\n\tCDROM_HCHPCTL_BFRD = 1 << 7\n} CDROMHCHPCTLFlag;\n\ntypedef enum {\n\tCDROM_HCLRCTL_CLRINT_BITMASK = 7 << 0,\n\tCDROM_HCLRCTL_CLRINT0        = 1 << 0,\n\tCDROM_HCLRCTL_CLRINT1        = 1 << 1,\n\tCDROM_HCLRCTL_CLRINT2        = 1 << 2,\n\tCDROM_HCLRCTL_CLRBFEMPT      = 1 << 3,\n\tCDROM_HCLRCTL_CLRBFWRDY      = 1 << 4,\n\tCDROM_HCLRCTL_SMADPCLR       = 1 << 5,\n\tCDROM_HCLRCTL_CLRPRM         = 1 << 6,\n\tCDROM_HCLRCTL_CHPRST         = 1 << 7\n} CDROMHCLRCTLFlag;\n\ntypedef enum {\n\tCDROM_CI_SM       = 1 << 0,\n\tCDROM_CI_FS       = 1 << 2,\n\tCDROM_CI_BITLNGTH = 1 << 4,\n\tCDROM_CI_EMPHASIS = 1 << 6\n} CDROMCIFlag;\n\ntypedef enum {\n\tCDROM_ADPCTL_ADPMUTE = 1 << 0,\n\tCDROM_ADPCTL_CHNGATV = 1 << 5\n} CDROMADPCTLFlag;\n\n#define CDROM_HSTS      _MMIO8(IO_BASE | 0x800) // All banks\n#define CDROM_RESULT    _MMIO8(IO_BASE | 0x801) // All banks\n#define CDROM_RDDATA    _MMIO8(IO_BASE | 0x802) // All banks\n#define CDROM_HINTMSK_R _MMIO8(IO_BASE | 0x803) // Bank 0\n#define CDROM_HINTSTS   _MMIO8(IO_BASE | 0x803) // Bank 1\n\n#define CDROM_ADDRESS   _MMIO8(IO_BASE | 0x800) // All banks\n#define CDROM_COMMAND   _MMIO8(IO_BASE | 0x801) // Bank 0\n#define CDROM_PARAMETER _MMIO8(IO_BASE | 0x802) // Bank 0\n#define CDROM_HCHPCTL   _MMIO8(IO_BASE | 0x803) // Bank 0\n#define CDROM_WRDATA    _MMIO8(IO_BASE | 0x801) // Bank 1\n#define CDROM_HINTMSK_W _MMIO8(IO_BASE | 0x802) // Bank 1\n#define CDROM_HCLRCTL   _MMIO8(IO_BASE | 0x803) // Bank 1\n#define CDROM_CI        _MMIO8(IO_BASE | 0x801) // Bank 2\n#define CDROM_ATV0      _MMIO8(IO_BASE | 0x802) // Bank 2\n#define CDROM_ATV1      _MMIO8(IO_BASE | 0x803) // Bank 2\n#define CDROM_ATV2      _MMIO8(IO_BASE | 0x801) // Bank 3\n#define CDROM_ATV3      _MMIO8(IO_BASE | 0x802) // Bank 3\n#define CDROM_ADPCTL    _MMIO8(IO_BASE | 0x803) // Bank 3\n\n/* GPU */\n\ntypedef enum {\n\tGP1_STAT_PAGE_X_BITMASK      = 15 <<  0, // GP0_CMD_TPAGE\n\tGP1_STAT_PAGE_Y0             =  1 <<  4, // GP0_CMD_TPAGE\n\tGP1_STAT_BLEND_BITMASK       =  3 <<  5, // GP0_CMD_TPAGE\n\tGP1_STAT_BLEND_SEMITRANS     =  0 <<  5, // GP0_CMD_TPAGE\n\tGP1_STAT_BLEND_ADD           =  1 <<  5, // GP0_CMD_TPAGE\n\tGP1_STAT_BLEND_SUBTRACT      =  2 <<  5, // GP0_CMD_TPAGE\n\tGP1_STAT_BLEND_DIV4_ADD      =  3 <<  5, // GP0_CMD_TPAGE\n\tGP1_STAT_COLOR_BITMASK       =  3 <<  7, // GP0_CMD_TPAGE\n\tGP1_STAT_COLOR_4BPP          =  0 <<  7, // GP0_CMD_TPAGE\n\tGP1_STAT_COLOR_8BPP          =  1 <<  7, // GP0_CMD_TPAGE\n\tGP1_STAT_COLOR_16BPP         =  2 <<  7, // GP0_CMD_TPAGE\n\tGP1_STAT_DITHER              =  1 <<  9, // GP0_CMD_TPAGE\n\tGP1_STAT_UNLOCK_FB           =  1 << 10, // GP0_CMD_TPAGE\n\tGP1_STAT_SET_MASK            =  1 << 11, // GP0_CMD_FB_MASK\n\tGP1_STAT_USE_MASK            =  1 << 12, // GP0_CMD_FB_MASK\n\tGP1_STAT_DISP_FIELD_BITMASK  =  1 << 13,\n\tGP1_STAT_DISP_FIELD_EVEN     =  0 << 13,\n\tGP1_STAT_DISP_FIELD_ODD      =  1 << 13,\n\tGP1_STAT_PAGE_Y1             =  1 << 15, // GP0_CMD_TPAGE\n\tGP1_STAT_FB_HRES_BITMASK     =  7 << 16, // GP1_CMD_FB_MODE\n\tGP1_STAT_FB_VRES_BITMASK     =  1 << 19, // GP1_CMD_FB_MODE\n\tGP1_STAT_FB_VRES_256         =  0 << 19, // GP1_CMD_FB_MODE\n\tGP1_STAT_FB_VRES_512         =  1 << 19, // GP1_CMD_FB_MODE\n\tGP1_STAT_FB_MODE_BITMASK     =  1 << 20, // GP1_CMD_FB_MODE\n\tGP1_STAT_FB_MODE_NTSC        =  0 << 20, // GP1_CMD_FB_MODE\n\tGP1_STAT_FB_MODE_PAL         =  1 << 20, // GP1_CMD_FB_MODE\n\tGP1_STAT_FB_COLOR_BITMASK    =  1 << 21, // GP1_CMD_FB_MODE\n\tGP1_STAT_FB_COLOR_16BPP      =  0 << 21, // GP1_CMD_FB_MODE\n\tGP1_STAT_FB_COLOR_24BPP      =  1 << 21, // GP1_CMD_FB_MODE\n\tGP1_STAT_FB_INTERLACE        =  1 << 22, // GP1_CMD_FB_MODE\n\tGP1_STAT_DISP_BLANK          =  1 << 23, // GP1_CMD_DISP_BLANK\n\tGP1_STAT_IRQ                 =  1 << 24,\n\tGP1_STAT_DREQ                =  1 << 25,\n\tGP1_STAT_CMD_READY           =  1 << 26,\n\tGP1_STAT_READ_READY          =  1 << 27,\n\tGP1_STAT_WRITE_READY         =  1 << 28,\n\tGP1_STAT_DREQ_MODE_BITMASK   =  3 << 29, // GP1_CMD_DREQ_MODE\n\tGP1_STAT_DREQ_MODE_NONE      =  0 << 29, // GP1_CMD_DREQ_MODE\n\tGP1_STAT_DREQ_MODE_FIFO      =  1 << 29, // GP1_CMD_DREQ_MODE\n\tGP1_STAT_DREQ_MODE_GP0_WRITE =  2 << 29, // GP1_CMD_DREQ_MODE\n\tGP1_STAT_DREQ_MODE_GP0_READ  =  3 << 29, // GP1_CMD_DREQ_MODE\n\tGP1_STAT_DRAW_FIELD_BITMASK  =  1 << 31,\n\tGP1_STAT_DRAW_FIELD_EVEN     =  0 << 31,\n\tGP1_STAT_DRAW_FIELD_ODD      =  1 << 31\n} GP1StatusFlag;\n\n#define GPU_GP0 _MMIO32(IO_BASE | 0x810)\n#define GPU_GP1 _MMIO32(IO_BASE | 0x814)\n\n/* MDEC */\n\ntypedef enum {\n\tMDEC_CMD_LENGTH_BITMASK     = 0xffff <<  0, // MDEC_CMD_OP_DECODE\n\tMDEC_CMD_USE_CHROMA         =      1 <<  0, // MDEC_CMD_OP_SET_QUANT_TABLE\n\tMDEC_CMD_SIGNED             =      1 << 25, // MDEC_CMD_OP_DECODE\n\tMDEC_CMD_16BPP_MASK         =      1 << 26, // MDEC_CMD_OP_DECODE\n\tMDEC_CMD_FORMAT_BITMASK     =      3 << 27, // MDEC_CMD_OP_DECODE\n\tMDEC_CMD_FORMAT_4BPP        =      0 << 27, // MDEC_CMD_OP_DECODE\n\tMDEC_CMD_FORMAT_8BPP        =      1 << 27, // MDEC_CMD_OP_DECODE\n\tMDEC_CMD_FORMAT_24BPP       =      2 << 27, // MDEC_CMD_OP_DECODE\n\tMDEC_CMD_FORMAT_16BPP       =      3 << 27, // MDEC_CMD_OP_DECODE\n\tMDEC_CMD_OP_BITMASK         =      7 << 29,\n\tMDEC_CMD_OP_NOP             =      0 << 29,\n\tMDEC_CMD_OP_DECODE          =      1 << 29,\n\tMDEC_CMD_OP_SET_QUANT_TABLE =      2 << 29,\n\tMDEC_CMD_OP_SET_IDCT_TABLE  =      3 << 29\n} MDECCommandFlag;\n\ntypedef enum {\n\tMDEC_STAT_LENGTH_BITMASK = 0xffff <<  0,\n\tMDEC_STAT_BLOCK_BITMASK  =      7 << 16,\n\tMDEC_STAT_BLOCK_Y0       =      0 << 16,\n\tMDEC_STAT_BLOCK_Y1       =      1 << 16,\n\tMDEC_STAT_BLOCK_Y2       =      2 << 16,\n\tMDEC_STAT_BLOCK_Y3       =      3 << 16,\n\tMDEC_STAT_BLOCK_CR       =      4 << 16,\n\tMDEC_STAT_BLOCK_CB       =      5 << 16,\n\tMDEC_STAT_16BPP_MASK     =      1 << 23,\n\tMDEC_STAT_SIGNED         =      1 << 24,\n\tMDEC_STAT_FORMAT_BITMASK =      3 << 25,\n\tMDEC_STAT_FORMAT_4BPP    =      0 << 25,\n\tMDEC_STAT_FORMAT_8BPP    =      1 << 25,\n\tMDEC_STAT_FORMAT_24BPP   =      2 << 25,\n\tMDEC_STAT_FORMAT_16BPP   =      3 << 25,\n\tMDEC_STAT_DREQ_OUT       =      1 << 27,\n\tMDEC_STAT_DREQ_IN        =      1 << 28,\n\tMDEC_STAT_BUSY           =      1 << 29,\n\tMDEC_STAT_DATA_FULL      =      1 << 30,\n\tMDEC_STAT_DATA_EMPTY     =      1 << 31\n} MDECStatusFlag;\n\ntypedef enum {\n\tMDEC_CTRL_DMA_OUT = 1 << 29,\n\tMDEC_CTRL_DMA_IN  = 1 << 30,\n\tMDEC_CTRL_RESET   = 1 << 31\n} MDECControlFlag;\n\n#define MDEC0 _MMIO32(IO_BASE | 0x820)\n#define MDEC1 _MMIO32(IO_BASE | 0x824)\n\n/* SPU */\n\ntypedef enum {\n\tSPU_STAT_I2SA_ENABLE    = 1 <<  0,\n\tSPU_STAT_I2SB_ENABLE    = 1 <<  1,\n\tSPU_STAT_I2SA_REVERB    = 1 <<  2,\n\tSPU_STAT_I2SB_REVERB    = 1 <<  3,\n\tSPU_STAT_XFER_BITMASK   = 3 <<  4,\n\tSPU_STAT_XFER_NONE      = 0 <<  4,\n\tSPU_STAT_XFER_WRITE     = 1 <<  4,\n\tSPU_STAT_XFER_DMA_WRITE = 2 <<  4,\n\tSPU_STAT_XFER_DMA_READ  = 3 <<  4,\n\tSPU_STAT_IRQ            = 1 <<  6,\n\tSPU_STAT_DREQ           = 1 <<  7,\n\tSPU_STAT_WRITE_REQ      = 1 <<  8,\n\tSPU_STAT_READ_REQ       = 1 <<  9,\n\tSPU_STAT_BUSY           = 1 << 10,\n\tSPU_STAT_CAPTURE_BUF    = 1 << 11\n} SPUStatusFlag;\n\ntypedef enum {\n\tSPU_CTRL_I2SA_ENABLE    = 1 <<  0,\n\tSPU_CTRL_I2SB_ENABLE    = 1 <<  1,\n\tSPU_CTRL_I2SA_REVERB    = 1 <<  2,\n\tSPU_CTRL_I2SB_REVERB    = 1 <<  3,\n\tSPU_CTRL_XFER_BITMASK   = 3 <<  4,\n\tSPU_CTRL_XFER_NONE      = 0 <<  4,\n\tSPU_CTRL_XFER_WRITE     = 1 <<  4,\n\tSPU_CTRL_XFER_DMA_WRITE = 2 <<  4,\n\tSPU_CTRL_XFER_DMA_READ  = 3 <<  4,\n\tSPU_CTRL_IRQ_ENABLE     = 1 <<  6,\n\tSPU_CTRL_REVERB_ENABLE  = 1 <<  7,\n\tSPU_CTRL_DAC_ENABLE     = 1 << 14,\n\tSPU_CTRL_ENABLE         = 1 << 15\n} SPUControlFlag;\n\n#define SPU_CH_VOLL(N)  _MMIO16((IO_BASE | 0xc00) + (16 * (N)))\n#define SPU_CH_VOLR(N)  _MMIO16((IO_BASE | 0xc02) + (16 * (N)))\n#define SPU_CH_PITCH(N) _MMIO16((IO_BASE | 0xc04) + (16 * (N)))\n#define SPU_CH_SSA(N)   _MMIO16((IO_BASE | 0xc06) + (16 * (N)))\n#define SPU_CH_ADSR1(N) _MMIO16((IO_BASE | 0xc08) + (16 * (N)))\n#define SPU_CH_ADSR2(N) _MMIO16((IO_BASE | 0xc0a) + (16 * (N)))\n#define SPU_CH_ENVX(N)  _MMIO16((IO_BASE | 0xc0c) + (16 * (N)))\n#define SPU_CH_LSAX(N)  _MMIO16((IO_BASE | 0xc0e) + (16 * (N)))\n\n#define SPU_MVOLL _MMIO16(IO_BASE | 0xd80)\n#define SPU_MVOLR _MMIO16(IO_BASE | 0xd82)\n#define SPU_EVOLL _MMIO16(IO_BASE | 0xd84)\n#define SPU_EVOLR _MMIO16(IO_BASE | 0xd86)\n#define SPU_KON0  _MMIO16(IO_BASE | 0xd88)\n#define SPU_KON1  _MMIO16(IO_BASE | 0xd8a)\n#define SPU_KOFF0 _MMIO16(IO_BASE | 0xd8c)\n#define SPU_KOFF1 _MMIO16(IO_BASE | 0xd8e)\n#define SPU_PMON0 _MMIO16(IO_BASE | 0xd90)\n#define SPU_PMON1 _MMIO16(IO_BASE | 0xd92)\n#define SPU_NON0  _MMIO16(IO_BASE | 0xd94)\n#define SPU_NON1  _MMIO16(IO_BASE | 0xd96)\n#define SPU_EON0  _MMIO16(IO_BASE | 0xd98)\n#define SPU_EON1  _MMIO16(IO_BASE | 0xd9a)\n#define SPU_ENDX0 _MMIO16(IO_BASE | 0xd9c)\n#define SPU_ENDX1 _MMIO16(IO_BASE | 0xd9e)\n\n#define SPU_ESA      _MMIO16(IO_BASE | 0xda2)\n#define SPU_IRQA     _MMIO16(IO_BASE | 0xda4)\n#define SPU_TSA      _MMIO16(IO_BASE | 0xda6)\n#define SPU_DATA     _MMIO16(IO_BASE | 0xda8)\n#define SPU_CTRL     _MMIO16(IO_BASE | 0xdaa)\n#define SPU_RAM_CTRL _MMIO16(IO_BASE | 0xdac)\n#define SPU_STAT     _MMIO16(IO_BASE | 0xdae)\n\n#define SPU_AVOLL  _MMIO16(IO_BASE | 0xdb0)\n#define SPU_AVOLR  _MMIO16(IO_BASE | 0xdb2)\n#define SPU_BVOLL  _MMIO16(IO_BASE | 0xdb4)\n#define SPU_BVOLR  _MMIO16(IO_BASE | 0xdb6)\n#define SPU_MVOLXL _MMIO16(IO_BASE | 0xdb8)\n#define SPU_MVOLXR _MMIO16(IO_BASE | 0xdba)\n\n#define SPU_REVERB_dAPF1   _MMIO16(IO_BASE | 0xdc0)\n#define SPU_REVERB_dAPF2   _MMIO16(IO_BASE | 0xdc2)\n#define SPU_REVERB_vIIR    _MMIO16(IO_BASE | 0xdc4)\n#define SPU_REVERB_vCOMB1  _MMIO16(IO_BASE | 0xdc6)\n#define SPU_REVERB_vCOMB2  _MMIO16(IO_BASE | 0xdc8)\n#define SPU_REVERB_vCOMB3  _MMIO16(IO_BASE | 0xdca)\n#define SPU_REVERB_vCOMB4  _MMIO16(IO_BASE | 0xdcc)\n#define SPU_REVERB_vWALL   _MMIO16(IO_BASE | 0xdce)\n#define SPU_REVERB_vAPF1   _MMIO16(IO_BASE | 0xdd0)\n#define SPU_REVERB_vAPF2   _MMIO16(IO_BASE | 0xdd2)\n#define SPU_REVERB_mLSAME  _MMIO16(IO_BASE | 0xdd4)\n#define SPU_REVERB_mRSAME  _MMIO16(IO_BASE | 0xdd6)\n#define SPU_REVERB_mLCOMB1 _MMIO16(IO_BASE | 0xdd8)\n#define SPU_REVERB_mRCOMB1 _MMIO16(IO_BASE | 0xdda)\n#define SPU_REVERB_mLCOMB2 _MMIO16(IO_BASE | 0xddc)\n#define SPU_REVERB_mRCOMB2 _MMIO16(IO_BASE | 0xdde)\n#define SPU_REVERB_dLSAME  _MMIO16(IO_BASE | 0xde0)\n#define SPU_REVERB_dRSAME  _MMIO16(IO_BASE | 0xde2)\n#define SPU_REVERB_mLDIFF  _MMIO16(IO_BASE | 0xde4)\n#define SPU_REVERB_mRDIFF  _MMIO16(IO_BASE | 0xde6)\n#define SPU_REVERB_mLCOMB3 _MMIO16(IO_BASE | 0xde8)\n#define SPU_REVERB_mRCOMB3 _MMIO16(IO_BASE | 0xdea)\n#define SPU_REVERB_mLCOMB4 _MMIO16(IO_BASE | 0xdec)\n#define SPU_REVERB_mRCOMB4 _MMIO16(IO_BASE | 0xdee)\n#define SPU_REVERB_dLDIFF  _MMIO16(IO_BASE | 0xdf0)\n#define SPU_REVERB_dRDIFF  _MMIO16(IO_BASE | 0xdf2)\n#define SPU_REVERB_mLAPF1  _MMIO16(IO_BASE | 0xdf4)\n#define SPU_REVERB_mRAPF1  _MMIO16(IO_BASE | 0xdf6)\n#define SPU_REVERB_mLAPF2  _MMIO16(IO_BASE | 0xdf8)\n#define SPU_REVERB_mRAPF2  _MMIO16(IO_BASE | 0xdfa)\n#define SPU_REVERB_vLIN    _MMIO16(IO_BASE | 0xdfc)\n#define SPU_REVERB_vRIN    _MMIO16(IO_BASE | 0xdfe)\n\n#define SPU_CH_VOLXL(N) _MMIO16((IO_BASE | 0xe00) + (4 * (N)))\n#define SPU_CH_VOLXR(N) _MMIO16((IO_BASE | 0xe02) + (4 * (N)))\n\n/* CW33300 CPU configuration */\n\ntypedef enum {\n\tCPU_BCC_LOCK           = 1 <<  0,\n\tCPU_BCC_INV            = 1 <<  1,\n\tCPU_BCC_TAG            = 1 <<  2,\n\tCPU_BCC_RAM            = 1 <<  3,\n\tCPU_BCC_DBLKSZ_BITMASK = 3 <<  4,\n\tCPU_BCC_DBLKSZ_2       = 0 <<  4,\n\tCPU_BCC_DBLKSZ_4       = 1 <<  4,\n\tCPU_BCC_DS             = 1 <<  7,\n\tCPU_BCC_IBLKSZ_BITMASK = 3 <<  8,\n\tCPU_BCC_IBLKSZ_2       = 0 <<  8,\n\tCPU_BCC_IBLKSZ_4       = 1 <<  8,\n\tCPU_BCC_IS0            = 1 << 10,\n\tCPU_BCC_IS1            = 1 << 11,\n\tCPU_BCC_INTP           = 1 << 12,\n\tCPU_BCC_RDPRI          = 1 << 13,\n\tCPU_BCC_NOPAD          = 1 << 14,\n\tCPU_BCC_BGNT           = 1 << 15,\n\tCPU_BCC_LDSCH          = 1 << 16,\n\tCPU_BCC_NOSTR          = 1 << 17\n} CPUBCCFlag;\n\n#define CPU_BCC _MMIO32(CPU_BASE | 0x130)\n"
  },
  {
    "path": "src/vendor/LICENSE.printf",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014 Marco Paland\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "src/vendor/printf.c",
    "content": "///////////////////////////////////////////////////////////////////////////////\r\n// \\author (c) Marco Paland (info@paland.com)\r\n//             2014-2019, PALANDesign Hannover, Germany\r\n//\r\n// \\license The MIT License (MIT)\r\n//\r\n// Permission is hereby granted, free of charge, to any person obtaining a copy\r\n// of this software and associated documentation files (the \"Software\"), to deal\r\n// in the Software without restriction, including without limitation the rights\r\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n// copies of the Software, and to permit persons to whom the Software is\r\n// furnished to do so, subject to the following conditions:\r\n//\r\n// The above copyright notice and this permission notice shall be included in\r\n// all copies or substantial portions of the Software.\r\n//\r\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n// THE SOFTWARE.\r\n//\r\n// \\brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on\r\n//        embedded systems with a very limited resources. These routines are thread\r\n//        safe and reentrant!\r\n//        Use this instead of the bloated standard/newlib printf cause these use\r\n//        malloc for printf (and may not be thread safe).\r\n//\r\n///////////////////////////////////////////////////////////////////////////////\r\n\r\n#include <stdbool.h>\r\n#include <stdint.h>\r\n\r\n#include \"printf.h\"\r\n\r\n\r\n// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the\r\n// printf_config.h header file\r\n// default: undefined\r\n#ifdef PRINTF_INCLUDE_CONFIG_H\r\n#include \"printf_config.h\"\r\n#endif\r\n\r\n\r\n// 'ntoa' conversion buffer size, this must be big enough to hold one converted\r\n// numeric number including padded zeros (dynamically created on stack)\r\n// default: 32 byte\r\n#ifndef PRINTF_NTOA_BUFFER_SIZE\r\n#define PRINTF_NTOA_BUFFER_SIZE    32U\r\n#endif\r\n\r\n// 'ftoa' conversion buffer size, this must be big enough to hold one converted\r\n// float number including padded zeros (dynamically created on stack)\r\n// default: 32 byte\r\n#ifndef PRINTF_FTOA_BUFFER_SIZE\r\n#define PRINTF_FTOA_BUFFER_SIZE    32U\r\n#endif\r\n\r\n// support for the floating point type (%f)\r\n// default: disabled\r\n//#define PRINTF_SUPPORT_FLOAT\r\n\r\n// support for exponential floating point notation (%e/%g)\r\n// default: activated\r\n#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL\r\n#define PRINTF_SUPPORT_EXPONENTIAL\r\n#endif\r\n\r\n// define the default floating point precision\r\n// default: 6 digits\r\n#ifndef PRINTF_DEFAULT_FLOAT_PRECISION\r\n#define PRINTF_DEFAULT_FLOAT_PRECISION  6U\r\n#endif\r\n\r\n// define the largest float suitable to print with %f\r\n// default: 1e9\r\n#ifndef PRINTF_MAX_FLOAT\r\n#define PRINTF_MAX_FLOAT  1e9\r\n#endif\r\n\r\n// support for the long long types (%llu or %p)\r\n// default: activated\r\n#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG\r\n#define PRINTF_SUPPORT_LONG_LONG\r\n#endif\r\n\r\n// support for the ptrdiff_t type (%t)\r\n// ptrdiff_t is normally defined in <stddef.h> as long or long long type\r\n// default: activated\r\n#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T\r\n#define PRINTF_SUPPORT_PTRDIFF_T\r\n#endif\r\n\r\n///////////////////////////////////////////////////////////////////////////////\r\n\r\n// internal flag definitions\r\n#define FLAGS_ZEROPAD   (1U <<  0U)\r\n#define FLAGS_LEFT      (1U <<  1U)\r\n#define FLAGS_PLUS      (1U <<  2U)\r\n#define FLAGS_SPACE     (1U <<  3U)\r\n#define FLAGS_HASH      (1U <<  4U)\r\n#define FLAGS_UPPERCASE (1U <<  5U)\r\n#define FLAGS_CHAR      (1U <<  6U)\r\n#define FLAGS_SHORT     (1U <<  7U)\r\n#define FLAGS_LONG      (1U <<  8U)\r\n#define FLAGS_LONG_LONG (1U <<  9U)\r\n#define FLAGS_PRECISION (1U << 10U)\r\n#define FLAGS_ADAPT_EXP (1U << 11U)\r\n\r\n\r\n// import float.h for DBL_MAX\r\n#if defined(PRINTF_SUPPORT_FLOAT)\r\n#include <float.h>\r\n#endif\r\n\r\n\r\n// output function type\r\ntypedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen);\r\n\r\n\r\n// wrapper (used as buffer) for output function type\r\ntypedef struct {\r\n  void  (*fct)(char character, void* arg);\r\n  void* arg;\r\n} out_fct_wrap_type;\r\n\r\n\r\n// internal buffer output\r\nstatic inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen)\r\n{\r\n  if (idx < maxlen) {\r\n    ((char*)buffer)[idx] = character;\r\n  }\r\n}\r\n\r\n\r\n// internal null output\r\nstatic inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen)\r\n{\r\n  (void)character; (void)buffer; (void)idx; (void)maxlen;\r\n}\r\n\r\n\r\n// internal _putchar wrapper\r\nstatic inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen)\r\n{\r\n  (void)buffer; (void)idx; (void)maxlen;\r\n  if (character) {\r\n    _putchar(character);\r\n  }\r\n}\r\n\r\n\r\n// internal output function wrapper\r\nstatic inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen)\r\n{\r\n  (void)idx; (void)maxlen;\r\n  if (character) {\r\n    // buffer is the output fct pointer\r\n    ((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg);\r\n  }\r\n}\r\n\r\n\r\n// internal secure strlen\r\n// \\return The length of the string (excluding the terminating 0) limited by 'maxsize'\r\nstatic inline unsigned int _strnlen_s(const char* str, size_t maxsize)\r\n{\r\n  const char* s;\r\n  for (s = str; *s && maxsize--; ++s);\r\n  return (unsigned int)(s - str);\r\n}\r\n\r\n\r\n// internal test if char is a digit (0-9)\r\n// \\return true if char is a digit\r\nstatic inline bool _is_digit(char ch)\r\n{\r\n  return (ch >= '0') && (ch <= '9');\r\n}\r\n\r\n\r\n// internal ASCII string to unsigned int conversion\r\nstatic unsigned int _atoi(const char** str)\r\n{\r\n  unsigned int i = 0U;\r\n  while (_is_digit(**str)) {\r\n    i = i * 10U + (unsigned int)(*((*str)++) - '0');\r\n  }\r\n  return i;\r\n}\r\n\r\n\r\n// output the specified string in reverse, taking care of any zero-padding\r\nstatic size_t _out_rev(out_fct_type out, char* buffer, size_t idx, size_t maxlen, const char* buf, size_t len, unsigned int width, unsigned int flags)\r\n{\r\n  const size_t start_idx = idx;\r\n\r\n  // pad spaces up to given width\r\n  if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {\r\n    for (size_t i = len; i < width; i++) {\r\n      out(' ', buffer, idx++, maxlen);\r\n    }\r\n  }\r\n\r\n  // reverse string\r\n  while (len) {\r\n    out(buf[--len], buffer, idx++, maxlen);\r\n  }\r\n\r\n  // append pad spaces up to given width\r\n  if (flags & FLAGS_LEFT) {\r\n    while (idx - start_idx < width) {\r\n      out(' ', buffer, idx++, maxlen);\r\n    }\r\n  }\r\n\r\n  return idx;\r\n}\r\n\r\n\r\n// internal itoa format\r\nstatic size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags)\r\n{\r\n  // pad leading zeros\r\n  if (!(flags & FLAGS_LEFT)) {\r\n    if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {\r\n      width--;\r\n    }\r\n    while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) {\r\n      buf[len++] = '0';\r\n    }\r\n    while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) {\r\n      buf[len++] = '0';\r\n    }\r\n  }\r\n\r\n  // handle hash\r\n  if (flags & FLAGS_HASH) {\r\n    if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) {\r\n      len--;\r\n      if (len && (base == 16U)) {\r\n        len--;\r\n      }\r\n    }\r\n    if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {\r\n      buf[len++] = 'x';\r\n    }\r\n    else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {\r\n      buf[len++] = 'X';\r\n    }\r\n    else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) {\r\n      buf[len++] = 'b';\r\n    }\r\n    if (len < PRINTF_NTOA_BUFFER_SIZE) {\r\n      buf[len++] = '0';\r\n    }\r\n  }\r\n\r\n  if (len < PRINTF_NTOA_BUFFER_SIZE) {\r\n    if (negative) {\r\n      buf[len++] = '-';\r\n    }\r\n    else if (flags & FLAGS_PLUS) {\r\n      buf[len++] = '+';  // ignore the space if the '+' exists\r\n    }\r\n    else if (flags & FLAGS_SPACE) {\r\n      buf[len++] = ' ';\r\n    }\r\n  }\r\n\r\n  return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);\r\n}\r\n\r\n\r\n// internal itoa for 'long' type\r\nstatic size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags)\r\n{\r\n  char buf[PRINTF_NTOA_BUFFER_SIZE];\r\n  size_t len = 0U;\r\n\r\n  // no hash for 0 values\r\n  if (!value) {\r\n    flags &= ~FLAGS_HASH;\r\n  }\r\n\r\n  // write if precision != 0 and value is != 0\r\n  if (!(flags & FLAGS_PRECISION) || value) {\r\n    do {\r\n      const char digit = (char)(value % base);\r\n      buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;\r\n      value /= base;\r\n    } while (value && (len < PRINTF_NTOA_BUFFER_SIZE));\r\n  }\r\n\r\n  return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);\r\n}\r\n\r\n\r\n// internal itoa for 'long long' type\r\n#if defined(PRINTF_SUPPORT_LONG_LONG)\r\nstatic size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags)\r\n{\r\n  char buf[PRINTF_NTOA_BUFFER_SIZE];\r\n  size_t len = 0U;\r\n\r\n  // no hash for 0 values\r\n  if (!value) {\r\n    flags &= ~FLAGS_HASH;\r\n  }\r\n\r\n  // write if precision != 0 and value is != 0\r\n  if (!(flags & FLAGS_PRECISION) || value) {\r\n    do {\r\n      const char digit = (char)(value % base);\r\n      buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;\r\n      value /= base;\r\n    } while (value && (len < PRINTF_NTOA_BUFFER_SIZE));\r\n  }\r\n\r\n  return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);\r\n}\r\n#endif  // PRINTF_SUPPORT_LONG_LONG\r\n\r\n\r\n#if defined(PRINTF_SUPPORT_FLOAT)\r\n\r\n#if defined(PRINTF_SUPPORT_EXPONENTIAL)\r\n// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT\r\nstatic size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags);\r\n#endif\r\n\r\n\r\n// internal ftoa for fixed decimal floating point\r\nstatic size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)\r\n{\r\n  char buf[PRINTF_FTOA_BUFFER_SIZE];\r\n  size_t len  = 0U;\r\n  double diff = 0.0;\r\n\r\n  // powers of 10\r\n  static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };\r\n\r\n  // test for special values\r\n  if (value != value)\r\n    return _out_rev(out, buffer, idx, maxlen, \"nan\", 3, width, flags);\r\n  if (value < -DBL_MAX)\r\n    return _out_rev(out, buffer, idx, maxlen, \"fni-\", 4, width, flags);\r\n  if (value > DBL_MAX)\r\n    return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? \"fni+\" : \"fni\", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags);\r\n\r\n  // test for very large values\r\n  // standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad\r\n  if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) {\r\n#if defined(PRINTF_SUPPORT_EXPONENTIAL)\r\n    return _etoa(out, buffer, idx, maxlen, value, prec, width, flags);\r\n#else\r\n    return 0U;\r\n#endif\r\n  }\r\n\r\n  // test for negative\r\n  bool negative = false;\r\n  if (value < 0) {\r\n    negative = true;\r\n    value = 0 - value;\r\n  }\r\n\r\n  // set default precision, if not set explicitly\r\n  if (!(flags & FLAGS_PRECISION)) {\r\n    prec = PRINTF_DEFAULT_FLOAT_PRECISION;\r\n  }\r\n  // limit precision to 9, cause a prec >= 10 can lead to overflow errors\r\n  while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) {\r\n    buf[len++] = '0';\r\n    prec--;\r\n  }\r\n\r\n  int whole = (int)value;\r\n  double tmp = (value - whole) * pow10[prec];\r\n  unsigned long frac = (unsigned long)tmp;\r\n  diff = tmp - frac;\r\n\r\n  if (diff > 0.5) {\r\n    ++frac;\r\n    // handle rollover, e.g. case 0.99 with prec 1 is 1.0\r\n    if (frac >= pow10[prec]) {\r\n      frac = 0;\r\n      ++whole;\r\n    }\r\n  }\r\n  else if (diff < 0.5) {\r\n  }\r\n  else if ((frac == 0U) || (frac & 1U)) {\r\n    // if halfway, round up if odd OR if last digit is 0\r\n    ++frac;\r\n  }\r\n\r\n  if (prec == 0U) {\r\n    diff = value - (double)whole;\r\n    if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) {\r\n      // exactly 0.5 and ODD, then round up\r\n      // 1.5 -> 2, but 2.5 -> 2\r\n      ++whole;\r\n    }\r\n  }\r\n  else {\r\n    unsigned int count = prec;\r\n    // now do fractional part, as an unsigned number\r\n    while (len < PRINTF_FTOA_BUFFER_SIZE) {\r\n      --count;\r\n      buf[len++] = (char)(48U + (frac % 10U));\r\n      if (!(frac /= 10U)) {\r\n        break;\r\n      }\r\n    }\r\n    // add extra 0s\r\n    while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) {\r\n      buf[len++] = '0';\r\n    }\r\n    if (len < PRINTF_FTOA_BUFFER_SIZE) {\r\n      // add decimal\r\n      buf[len++] = '.';\r\n    }\r\n  }\r\n\r\n  // do whole part, number is reversed\r\n  while (len < PRINTF_FTOA_BUFFER_SIZE) {\r\n    buf[len++] = (char)(48 + (whole % 10));\r\n    if (!(whole /= 10)) {\r\n      break;\r\n    }\r\n  }\r\n\r\n  // pad leading zeros\r\n  if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) {\r\n    if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {\r\n      width--;\r\n    }\r\n    while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) {\r\n      buf[len++] = '0';\r\n    }\r\n  }\r\n\r\n  if (len < PRINTF_FTOA_BUFFER_SIZE) {\r\n    if (negative) {\r\n      buf[len++] = '-';\r\n    }\r\n    else if (flags & FLAGS_PLUS) {\r\n      buf[len++] = '+';  // ignore the space if the '+' exists\r\n    }\r\n    else if (flags & FLAGS_SPACE) {\r\n      buf[len++] = ' ';\r\n    }\r\n  }\r\n\r\n  return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);\r\n}\r\n\r\n\r\n#if defined(PRINTF_SUPPORT_EXPONENTIAL)\r\n// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse <m.jasperse@gmail.com>\r\nstatic size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)\r\n{\r\n  // check for NaN and special values\r\n  if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) {\r\n    return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags);\r\n  }\r\n\r\n  // determine the sign\r\n  const bool negative = value < 0;\r\n  if (negative) {\r\n    value = -value;\r\n  }\r\n\r\n  // default precision\r\n  if (!(flags & FLAGS_PRECISION)) {\r\n    prec = PRINTF_DEFAULT_FLOAT_PRECISION;\r\n  }\r\n\r\n  // determine the decimal exponent\r\n  // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c)\r\n  union {\r\n    uint64_t U;\r\n    double   F;\r\n  } conv;\r\n\r\n  conv.F = value;\r\n  int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023;           // effectively log2\r\n  conv.U = (conv.U & ((1ULL << 52U) - 1U)) | (1023ULL << 52U);  // drop the exponent so conv.F is now in [1,2)\r\n  // now approximate log10 from the log2 integer part and an expansion of ln around 1.5\r\n  int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168);\r\n  // now we want to compute 10^expval but we want to be sure it won't overflow\r\n  exp2 = (int)(expval * 3.321928094887362 + 0.5);\r\n  const double z  = expval * 2.302585092994046 - exp2 * 0.6931471805599453;\r\n  const double z2 = z * z;\r\n  conv.U = (uint64_t)(exp2 + 1023) << 52U;\r\n  // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex\r\n  conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14)))));\r\n  // correct for rounding errors\r\n  if (value < conv.F) {\r\n    expval--;\r\n    conv.F /= 10;\r\n  }\r\n\r\n  // the exponent format is \"%+03d\" and largest value is \"307\", so set aside 4-5 characters\r\n  unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U;\r\n\r\n  // in \"%g\" mode, \"prec\" is the number of *significant figures* not decimals\r\n  if (flags & FLAGS_ADAPT_EXP) {\r\n    // do we want to fall-back to \"%f\" mode?\r\n    if ((value >= 1e-4) && (value < 1e6)) {\r\n      if ((int)prec > expval) {\r\n        prec = (unsigned)((int)prec - expval - 1);\r\n      }\r\n      else {\r\n        prec = 0;\r\n      }\r\n      flags |= FLAGS_PRECISION;   // make sure _ftoa respects precision\r\n      // no characters in exponent\r\n      minwidth = 0U;\r\n      expval   = 0;\r\n    }\r\n    else {\r\n      // we use one sigfig for the whole part\r\n      if ((prec > 0) && (flags & FLAGS_PRECISION)) {\r\n        --prec;\r\n      }\r\n    }\r\n  }\r\n\r\n  // will everything fit?\r\n  unsigned int fwidth = width;\r\n  if (width > minwidth) {\r\n    // we didn't fall-back so subtract the characters required for the exponent\r\n    fwidth -= minwidth;\r\n  } else {\r\n    // not enough characters, so go back to default sizing\r\n    fwidth = 0U;\r\n  }\r\n  if ((flags & FLAGS_LEFT) && minwidth) {\r\n    // if we're padding on the right, DON'T pad the floating part\r\n    fwidth = 0U;\r\n  }\r\n\r\n  // rescale the float value\r\n  if (expval) {\r\n    value /= conv.F;\r\n  }\r\n\r\n  // output the floating part\r\n  const size_t start_idx = idx;\r\n  idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP);\r\n\r\n  // output the exponent part\r\n  if (minwidth) {\r\n    // output the exponential symbol\r\n    out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen);\r\n    // output the exponent value\r\n    idx = _ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval, expval < 0, 10, 0, minwidth-1, FLAGS_ZEROPAD | FLAGS_PLUS);\r\n    // might need to right-pad spaces\r\n    if (flags & FLAGS_LEFT) {\r\n      while (idx - start_idx < width) out(' ', buffer, idx++, maxlen);\r\n    }\r\n  }\r\n  return idx;\r\n}\r\n#endif  // PRINTF_SUPPORT_EXPONENTIAL\r\n#endif  // PRINTF_SUPPORT_FLOAT\r\n\r\n\r\n// internal vsnprintf\r\nstatic int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va)\r\n{\r\n  unsigned int flags, width, precision, n;\r\n  size_t idx = 0U;\r\n\r\n  if (!buffer) {\r\n    // use null output function\r\n    out = _out_null;\r\n  }\r\n\r\n  while (*format)\r\n  {\r\n    // format specifier?  %[flags][width][.precision][length]\r\n    if (*format != '%') {\r\n      // no\r\n      out(*format, buffer, idx++, maxlen);\r\n      format++;\r\n      continue;\r\n    }\r\n    else {\r\n      // yes, evaluate it\r\n      format++;\r\n    }\r\n\r\n    // evaluate flags\r\n    flags = 0U;\r\n    do {\r\n      switch (*format) {\r\n        case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break;\r\n        case '-': flags |= FLAGS_LEFT;    format++; n = 1U; break;\r\n        case '+': flags |= FLAGS_PLUS;    format++; n = 1U; break;\r\n        case ' ': flags |= FLAGS_SPACE;   format++; n = 1U; break;\r\n        case '#': flags |= FLAGS_HASH;    format++; n = 1U; break;\r\n        default :                                   n = 0U; break;\r\n      }\r\n    } while (n);\r\n\r\n    // evaluate width field\r\n    width = 0U;\r\n    if (_is_digit(*format)) {\r\n      width = _atoi(&format);\r\n    }\r\n    else if (*format == '*') {\r\n      const int w = va_arg(va, int);\r\n      if (w < 0) {\r\n        flags |= FLAGS_LEFT;    // reverse padding\r\n        width = (unsigned int)-w;\r\n      }\r\n      else {\r\n        width = (unsigned int)w;\r\n      }\r\n      format++;\r\n    }\r\n\r\n    // evaluate precision field\r\n    precision = 0U;\r\n    if (*format == '.') {\r\n      flags |= FLAGS_PRECISION;\r\n      format++;\r\n      if (_is_digit(*format)) {\r\n        precision = _atoi(&format);\r\n      }\r\n      else if (*format == '*') {\r\n        const int prec = (int)va_arg(va, int);\r\n        precision = prec > 0 ? (unsigned int)prec : 0U;\r\n        format++;\r\n      }\r\n    }\r\n\r\n    // evaluate length field\r\n    switch (*format) {\r\n      case 'l' :\r\n        flags |= FLAGS_LONG;\r\n        format++;\r\n        if (*format == 'l') {\r\n          flags |= FLAGS_LONG_LONG;\r\n          format++;\r\n        }\r\n        break;\r\n      case 'h' :\r\n        flags |= FLAGS_SHORT;\r\n        format++;\r\n        if (*format == 'h') {\r\n          flags |= FLAGS_CHAR;\r\n          format++;\r\n        }\r\n        break;\r\n#if defined(PRINTF_SUPPORT_PTRDIFF_T)\r\n      case 't' :\r\n        flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);\r\n        format++;\r\n        break;\r\n#endif\r\n      case 'j' :\r\n        flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);\r\n        format++;\r\n        break;\r\n      case 'z' :\r\n        flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);\r\n        format++;\r\n        break;\r\n      default :\r\n        break;\r\n    }\r\n\r\n    // evaluate specifier\r\n    switch (*format) {\r\n      case 'd' :\r\n      case 'i' :\r\n      case 'u' :\r\n      case 'x' :\r\n      case 'X' :\r\n      case 'o' :\r\n      case 'b' : {\r\n        // set the base\r\n        unsigned int base;\r\n        if (*format == 'x' || *format == 'X') {\r\n          base = 16U;\r\n        }\r\n        else if (*format == 'o') {\r\n          base =  8U;\r\n        }\r\n        else if (*format == 'b') {\r\n          base =  2U;\r\n        }\r\n        else {\r\n          base = 10U;\r\n          flags &= ~FLAGS_HASH;   // no hash for dec format\r\n        }\r\n        // uppercase\r\n        if (*format == 'X') {\r\n          flags |= FLAGS_UPPERCASE;\r\n        }\r\n\r\n        // no plus or space flag for u, x, X, o, b\r\n        if ((*format != 'i') && (*format != 'd')) {\r\n          flags &= ~(FLAGS_PLUS | FLAGS_SPACE);\r\n        }\r\n\r\n        // ignore '0' flag when precision is given\r\n        if (flags & FLAGS_PRECISION) {\r\n          flags &= ~FLAGS_ZEROPAD;\r\n        }\r\n\r\n        // convert the integer\r\n        if ((*format == 'i') || (*format == 'd')) {\r\n          // signed\r\n          if (flags & FLAGS_LONG_LONG) {\r\n#if defined(PRINTF_SUPPORT_LONG_LONG)\r\n            const long long value = va_arg(va, long long);\r\n            idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);\r\n#endif\r\n          }\r\n          else if (flags & FLAGS_LONG) {\r\n            const long value = va_arg(va, long);\r\n            idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);\r\n          }\r\n          else {\r\n            const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int);\r\n            idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);\r\n          }\r\n        }\r\n        else {\r\n          // unsigned\r\n          if (flags & FLAGS_LONG_LONG) {\r\n#if defined(PRINTF_SUPPORT_LONG_LONG)\r\n            idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags);\r\n#endif\r\n          }\r\n          else if (flags & FLAGS_LONG) {\r\n            idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags);\r\n          }\r\n          else {\r\n            const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int);\r\n            idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags);\r\n          }\r\n        }\r\n        format++;\r\n        break;\r\n      }\r\n#if defined(PRINTF_SUPPORT_FLOAT)\r\n      case 'f' :\r\n      case 'F' :\r\n        if (*format == 'F') flags |= FLAGS_UPPERCASE;\r\n        idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);\r\n        format++;\r\n        break;\r\n#if defined(PRINTF_SUPPORT_EXPONENTIAL)\r\n      case 'e':\r\n      case 'E':\r\n      case 'g':\r\n      case 'G':\r\n        if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP;\r\n        if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE;\r\n        idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);\r\n        format++;\r\n        break;\r\n#endif  // PRINTF_SUPPORT_EXPONENTIAL\r\n#endif  // PRINTF_SUPPORT_FLOAT\r\n      case 'c' : {\r\n        unsigned int l = 1U;\r\n        // pre padding\r\n        if (!(flags & FLAGS_LEFT)) {\r\n          while (l++ < width) {\r\n            out(' ', buffer, idx++, maxlen);\r\n          }\r\n        }\r\n        // char output\r\n        out((char)va_arg(va, int), buffer, idx++, maxlen);\r\n        // post padding\r\n        if (flags & FLAGS_LEFT) {\r\n          while (l++ < width) {\r\n            out(' ', buffer, idx++, maxlen);\r\n          }\r\n        }\r\n        format++;\r\n        break;\r\n      }\r\n\r\n      case 's' : {\r\n        const char* p = va_arg(va, char*);\r\n        unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1);\r\n        // pre padding\r\n        if (flags & FLAGS_PRECISION) {\r\n          l = (l < precision ? l : precision);\r\n        }\r\n        if (!(flags & FLAGS_LEFT)) {\r\n          while (l++ < width) {\r\n            out(' ', buffer, idx++, maxlen);\r\n          }\r\n        }\r\n        // string output\r\n        while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) {\r\n          out(*(p++), buffer, idx++, maxlen);\r\n        }\r\n        // post padding\r\n        if (flags & FLAGS_LEFT) {\r\n          while (l++ < width) {\r\n            out(' ', buffer, idx++, maxlen);\r\n          }\r\n        }\r\n        format++;\r\n        break;\r\n      }\r\n\r\n      case 'p' : {\r\n        width = sizeof(void*) * 2U;\r\n        flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE;\r\n#if defined(PRINTF_SUPPORT_LONG_LONG)\r\n        const bool is_ll = sizeof(uintptr_t) == sizeof(long long);\r\n        if (is_ll) {\r\n          idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void*), false, 16U, precision, width, flags);\r\n        }\r\n        else {\r\n#endif\r\n          idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void*)), false, 16U, precision, width, flags);\r\n#if defined(PRINTF_SUPPORT_LONG_LONG)\r\n        }\r\n#endif\r\n        format++;\r\n        break;\r\n      }\r\n\r\n      case '%' :\r\n        out('%', buffer, idx++, maxlen);\r\n        format++;\r\n        break;\r\n\r\n      default :\r\n        out(*format, buffer, idx++, maxlen);\r\n        format++;\r\n        break;\r\n    }\r\n  }\r\n\r\n  // termination\r\n  out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);\r\n\r\n  // return written chars without terminating \\0\r\n  return (int)idx;\r\n}\r\n\r\n\r\n///////////////////////////////////////////////////////////////////////////////\r\n\r\nint printf_(const char* format, ...)\r\n{\r\n  va_list va;\r\n  va_start(va, format);\r\n  char buffer[1];\r\n  const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va);\r\n  va_end(va);\r\n  return ret;\r\n}\r\n\r\n\r\nint sprintf_(char* buffer, const char* format, ...)\r\n{\r\n  va_list va;\r\n  va_start(va, format);\r\n  const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va);\r\n  va_end(va);\r\n  return ret;\r\n}\r\n\r\n\r\nint snprintf_(char* buffer, size_t count, const char* format, ...)\r\n{\r\n  va_list va;\r\n  va_start(va, format);\r\n  const int ret = _vsnprintf(_out_buffer, buffer, count, format, va);\r\n  va_end(va);\r\n  return ret;\r\n}\r\n\r\n\r\nint vprintf_(const char* format, va_list va)\r\n{\r\n  char buffer[1];\r\n  return _vsnprintf(_out_char, buffer, (size_t)-1, format, va);\r\n}\r\n\r\n\r\nint vsnprintf_(char* buffer, size_t count, const char* format, va_list va)\r\n{\r\n  return _vsnprintf(_out_buffer, buffer, count, format, va);\r\n}\r\n\r\n\r\nint fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...)\r\n{\r\n  va_list va;\r\n  va_start(va, format);\r\n  const out_fct_wrap_type out_fct_wrap = { out, arg };\r\n  const int ret = _vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va);\r\n  va_end(va);\r\n  return ret;\r\n}\r\n"
  },
  {
    "path": "src/vendor/printf.h",
    "content": "///////////////////////////////////////////////////////////////////////////////\r\n// \\author (c) Marco Paland (info@paland.com)\r\n//             2014-2019, PALANDesign Hannover, Germany\r\n//\r\n// \\license The MIT License (MIT)\r\n//\r\n// Permission is hereby granted, free of charge, to any person obtaining a copy\r\n// of this software and associated documentation files (the \"Software\"), to deal\r\n// in the Software without restriction, including without limitation the rights\r\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n// copies of the Software, and to permit persons to whom the Software is\r\n// furnished to do so, subject to the following conditions:\r\n// \r\n// The above copyright notice and this permission notice shall be included in\r\n// all copies or substantial portions of the Software.\r\n// \r\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n// THE SOFTWARE.\r\n//\r\n// \\brief Tiny printf, sprintf and snprintf implementation, optimized for speed on\r\n//        embedded systems with a very limited resources.\r\n//        Use this instead of bloated standard/newlib printf.\r\n//        These routines are thread safe and reentrant.\r\n//\r\n///////////////////////////////////////////////////////////////////////////////\r\n\r\n#ifndef _PRINTF_H_\r\n#define _PRINTF_H_\r\n\r\n#include <stdarg.h>\r\n#include <stddef.h>\r\n\r\n\r\n#ifdef __cplusplus\r\nextern \"C\" {\r\n#endif\r\n\r\n\r\n/**\r\n * Output a character to a custom device like UART, used by the printf() function\r\n * This function is declared here only. You have to write your custom implementation somewhere\r\n * \\param character Character to output\r\n */\r\nvoid _putchar(char character);\r\n\r\n\r\n/**\r\n * Tiny printf implementation\r\n * You have to implement _putchar if you use printf()\r\n * To avoid conflicts with the regular printf() API it is overridden by macro defines\r\n * and internal underscore-appended functions like printf_() are used\r\n * \\param format A string that specifies the format of the output\r\n * \\return The number of characters that are written into the array, not counting the terminating null character\r\n */\r\n#define printf printf_\r\nint printf_(const char* format, ...);\r\n\r\n\r\n/**\r\n * Tiny sprintf implementation\r\n * Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD!\r\n * \\param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output!\r\n * \\param format A string that specifies the format of the output\r\n * \\return The number of characters that are WRITTEN into the buffer, not counting the terminating null character\r\n */\r\n#define sprintf sprintf_\r\nint sprintf_(char* buffer, const char* format, ...);\r\n\r\n\r\n/**\r\n * Tiny snprintf/vsnprintf implementation\r\n * \\param buffer A pointer to the buffer where to store the formatted string\r\n * \\param count The maximum number of characters to store in the buffer, including a terminating null character\r\n * \\param format A string that specifies the format of the output\r\n * \\param va A value identifying a variable arguments list\r\n * \\return The number of characters that COULD have been written into the buffer, not counting the terminating\r\n *         null character. A value equal or larger than count indicates truncation. Only when the returned value\r\n *         is non-negative and less than count, the string has been completely written.\r\n */\r\n#define snprintf  snprintf_\r\n#define vsnprintf vsnprintf_\r\nint  snprintf_(char* buffer, size_t count, const char* format, ...);\r\nint vsnprintf_(char* buffer, size_t count, const char* format, va_list va);\r\n\r\n\r\n/**\r\n * Tiny vprintf implementation\r\n * \\param format A string that specifies the format of the output\r\n * \\param va A value identifying a variable arguments list\r\n * \\return The number of characters that are WRITTEN into the buffer, not counting the terminating null character\r\n */\r\n#define vprintf vprintf_\r\nint vprintf_(const char* format, va_list va);\r\n\r\n\r\n/**\r\n * printf with output function\r\n * You may use this as dynamic alternative to printf() with its fixed _putchar() output\r\n * \\param out An output function which takes one character and an argument pointer\r\n * \\param arg An argument pointer for user data passed to output function\r\n * \\param format A string that specifies the format of the output\r\n * \\return The number of characters that are sent to the output function, not counting the terminating null character\r\n */\r\nint fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...);\r\n\r\n\r\n#ifdef __cplusplus\r\n}\r\n#endif\r\n\r\n\r\n#endif  // _PRINTF_H_\r\n"
  },
  {
    "path": "tools/convertExecutable.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"ELF executable to PlayStation 1 .EXE converter\n\nA very simple script to convert ELF files compiled for the PS1 to the executable\nformat used by the BIOS, with support for setting initial $sp/$gp values and\ncustomizing the region string (used by some emulators to determine whether they\nshould start in PAL or NTSC mode by default). Requires no external dependencies.\n\"\"\"\n\n__version__ = \"0.1.3\"\n__author__  = \"spicyjpeg\"\n\nfrom argparse        import ArgumentParser, FileType, Namespace\nfrom collections.abc import Generator\nfrom dataclasses     import dataclass\nfrom enum            import IntEnum, IntFlag\nfrom struct          import Struct\nfrom typing          import BinaryIO\n\n## Utilities\n\ndef alignToMultiple(data: bytearray, alignment: int):\n\tpadAmount: int = alignment - (len(data) % alignment)\n\n\tif padAmount < alignment:\n\t\tdata.extend(b\"\\0\" * padAmount)\n\ndef parseStructFromFile(file: BinaryIO, _struct: Struct) -> tuple:\n\treturn _struct.unpack(file.read(_struct.size))\n\ndef parseStructsFromFile(\n\tfile: BinaryIO, _struct: Struct, count: int\n) -> Generator[tuple, None, None]:\n\tdata: bytes = file.read(_struct.size * count)\n\n\tfor offset in range(0, len(data), _struct.size):\n\t\tyield _struct.unpack(data[offset:offset + _struct.size])\n\n## ELF file parser\n\nELF_HEADER_STRUCT:  Struct = Struct(\"< 4s 4B 8x 2H 5I 6H\")\nELF_HEADER_MAGIC:   bytes  = b\"\\x7fELF\"\nPROG_HEADER_STRUCT: Struct = Struct(\"< 8I\")\n\nclass ELFType(IntEnum):\n\tRELOCATABLE = 1\n\tEXECUTABLE  = 2\n\tSHARED      = 3\n\tCORE        = 4\n\nclass ELFArchitecture(IntEnum):\n\tMIPS = 8\n\nclass ELFEndianness(IntEnum):\n\tLITTLE = 1\n\tBIG    = 2\n\nclass ProgHeaderType(IntEnum):\n\tNONE        = 0\n\tLOAD        = 1\n\tDYNAMIC     = 2\n\tINTERPRETER = 3\n\tNOTE        = 4\n\nclass ProgHeaderFlag(IntFlag):\n\tEXECUTE = 1 << 0\n\tWRITE   = 1 << 1\n\tREAD    = 1 << 2\n\n@dataclass\nclass Segment:\n\taddress: int\n\tdata:    bytes\n\tflags:   ProgHeaderFlag\n\n\tdef isReadOnly(self) -> bool:\n\t\treturn not \\\n\t\t\t(self.flags & (ProgHeaderFlag.WRITE | ProgHeaderFlag.EXECUTE))\n\nclass ELF:\n\tdef __init__(self, file: BinaryIO):\n\t\t# Parse the file header and perform some minimal validation.\n\t\tfile.seek(0)\n\n\t\t(\n\t\t\tmagic,\n\t\t\twordSize,\n\t\t\tendianness,\n\t\t\t_,\n\t\t\tabi,\n\t\t\telfType,\n\t\t\tarchitecture,\n\t\t\t_,\n\t\t\tentryPoint,\n\t\t\tprogHeaderOffset,\n\t\t\tsecHeaderOffset,\n\t\t\tflags,\n\t\t\telfHeaderSize,\n\t\t\tprogHeaderSize,\n\t\t\tprogHeaderCount,\n\t\t\tsecHeaderSize,\n\t\t\tsecHeaderCount,\n\t\t\t_\n\t\t) = \\\n\t\t\tparseStructFromFile(file, ELF_HEADER_STRUCT)\n\n\t\tif magic != ELF_HEADER_MAGIC:\n\t\t\traise RuntimeError(\"file is not a valid ELF\")\n\t\tif wordSize != 1 or endianness != ELFEndianness.LITTLE:\n\t\t\traise RuntimeError(\"ELF file must be 32-bit little-endian\")\n\t\tif (\n\t\t\telfHeaderSize  != ELF_HEADER_STRUCT.size or\n\t\t\tprogHeaderSize != PROG_HEADER_STRUCT.size\n\t\t):\n\t\t\traise RuntimeError(\"unsupported ELF format\")\n\n\t\tself.type:         ELFType = ELFType(elfType)\n\t\tself.architecture: int     = architecture\n\t\tself.abi:          int     = abi\n\t\tself.entryPoint:   int     = entryPoint\n\t\tself.flags:        int     = flags\n\n\t\t# Parse the program headers and extract all loadable segments.\n\t\tself.segments: list[Segment] = []\n\n\t\tfile.seek(progHeaderOffset)\n\n\t\tfor (\n\t\t\theaderType,\n\t\t\tfileOffset,\n\t\t\taddress,\n\t\t\t_,\n\t\t\tlength,\n\t\t\t_,\n\t\t\tflags,\n\t\t\t_\n\t\t) in parseStructsFromFile(file, PROG_HEADER_STRUCT, progHeaderCount):\n\t\t\tif headerType != ProgHeaderType.LOAD:\n\t\t\t\tcontinue\n\n\t\t\tfile.seek(fileOffset)\n\t\t\tself.segments.append(Segment(address, file.read(length), flags))\n\n\t\t#file.close()\n\n\tdef flatten(self, stripReadOnly: bool = False) -> tuple[int, bytearray]:\n\t\t# Find the lower and upper boundaries of the segments' address space.\n\t\tstartAddress: int = min(\n\t\t\tseg.address for seg in self.segments\n\t\t)\n\t\tendAddress: int = max(\n\t\t\t(seg.address + len(seg.data)) for seg in self.segments\n\t\t)\n\n\t\t# Copy all segments into a new byte array at their respective offsets.\n\t\tdata: bytearray = bytearray(endAddress - startAddress)\n\n\t\tfor seg in self.segments:\n\t\t\tif stripReadOnly and seg.isReadOnly():\n\t\t\t\tcontinue\n\n\t\t\toffset: int = seg.address - startAddress\n\t\t\tdata[offset:offset + len(seg.data)] = seg.data\n\n\t\treturn startAddress, data\n\n## Main\n\nEXE_HEADER_STRUCT: Struct = Struct(\"< 8s 8x 4I 16x 2I 20x 1972s\")\nEXE_HEADER_MAGIC:  bytes  = b\"PS-X EXE\"\nEXE_ALIGNMENT:     int    = 2048\n\ndef createParser() -> ArgumentParser:\n\tparser = ArgumentParser(\n\t\tdescription = \\\n\t\t\t\"Converts an ELF executable into a PlayStation 1 .EXE file.\",\n\t\tadd_help    = False\n\t)\n\n\tgroup = parser.add_argument_group(\"Tool options\")\n\tgroup.add_argument(\n\t\t\"-h\", \"--help\",\n\t\taction = \"help\",\n\t\thelp   = \"Show this help message and exit\"\n\t)\n\n\tgroup = parser.add_argument_group(\"Conversion options\")\n\tgroup.add_argument(\n\t\t\"-r\", \"--region-str\",\n\t\ttype    = str,\n\t\tdefault = \"\",\n\t\thelp    = \"Add a custom region string to the header\",\n\t\tmetavar = \"string\"\n\t)\n\tgroup.add_argument(\n\t\t\"-s\", \"--set-sp\",\n\t\ttype    = lambda value: int(value, 0),\n\t\tdefault = 0,\n\t\thelp    = \"Add an initial value for the stack pointer to the header\",\n\t\tmetavar = \"value\"\n\t)\n\tgroup.add_argument(\n\t\t\"-g\", \"--set-gp\",\n\t\ttype    = lambda value: int(value, 0),\n\t\tdefault = 0,\n\t\thelp    = \"Add an initial value for the global pointer to the header\",\n\t\tmetavar = \"value\"\n\t)\n\tgroup.add_argument(\n\t\t\"-S\", \"--strip-read-only\",\n\t\taction = \"store_true\",\n\t\thelp   = \\\n\t\t\t\"Remove all ELF segments not marked writable nor executable from \"\n\t\t\t\"the output file\"\n\t)\n\n\tgroup = parser.add_argument_group(\"File paths\")\n\tgroup.add_argument(\n\t\t\"input\",\n\t\ttype = FileType(\"rb\"),\n\t\thelp = \"Path to ELF input executable\",\n\t)\n\tgroup.add_argument(\n\t\t\"output\",\n\t\ttype = FileType(\"wb\"),\n\t\thelp = \"Path to PS1 executable to generate\"\n\t)\n\n\treturn parser\n\ndef main():\n\tparser: ArgumentParser = createParser()\n\targs:   Namespace      = parser.parse_args()\n\n\twith args.input as file:\n\t\ttry:\n\t\t\telf: ELF = ELF(file)\n\t\texcept RuntimeError as err:\n\t\t\tparser.error(err.args[0])\n\n\tif elf.type != ELFType.EXECUTABLE:\n\t\tparser.error(\"ELF file must be an executable\")\n\tif elf.architecture != ELFArchitecture.MIPS:\n\t\tparser.error(\"ELF architecture must be MIPS\")\n\tif not elf.segments:\n\t\tparser.error(\"ELF file must contain at least one segment\")\n\n\tstartAddress, data = elf.flatten(args.strip_read_only)\n\talignToMultiple(data, EXE_ALIGNMENT)\n\n\tregion: bytes = args.region_str.strip().encode(\"ascii\")\n\theader: bytes = EXE_HEADER_STRUCT.pack(\n\t\tEXE_HEADER_MAGIC, # Magic\n\t\telf.entryPoint,   # Entry point\n\t\targs.set_gp,      # Initial global pointer\n\t\tstartAddress,     # Data load address\n\t\tlen(data),        # Data size\n\t\targs.set_sp,      # Stack offset\n\t\t0,                # Stack size\n\t\tregion            # Region string\n\t)\n\n\twith args.output as file:\n\t\tfile.write(header)\n\t\tfile.write(data)\n\nif __name__ == \"__main__\":\n\tmain()\n"
  },
  {
    "path": "tools/convertImage.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"PlayStation 1 image/texture data converter\n\nA simple script to convert image files into either raw 16bpp RGB data as\nexpected by the PS1's GPU, or 4bpp or 8bpp indexed color data plus a separate\n16bpp color palette. Requires PIL/Pillow and NumPy to be installed.\n\"\"\"\n\n__version__ = \"0.2.1\"\n__author__  = \"spicyjpeg\"\n\nfrom argparse import ArgumentParser, FileType, Namespace\n\nimport numpy as np\nfrom numpy.typing import NDArray\nfrom PIL          import Image\n\n## Input image handling\n\n# Pillow's built-in quantize() method will use different algorithms, some of\n# which are broken, depending on whether the input image has an alpha channel.\n# As a workaround, all images are \"quantized\" (with no dithering - images with\n# more colors than allowed are rejected) manually instead. This conversion is\n# performed on indexed color images as well in order to normalize their\n# palettes.\ndef quantizeImage(imageObj: Image.Image, numColors: int) -> Image.Image:\n\t#if imageObj.mode == \"P\":\n\t\t#return imageObj\n\tif imageObj.mode not in ( \"RGB\", \"RGBA\" ):\n\t\timageObj = imageObj.convert(\"RGBA\")\n\n\timage: NDArray[np.uint8] = np.asarray(imageObj, \"B\")\n\tclut, image              = np.unique(\n\t\timage.reshape(( -1, image.shape[2] )),\n\t\treturn_inverse = True,\n\t\taxis           = 0\n\t)\n\n\tif clut.shape[0] > numColors:\n\t\traise RuntimeError(\n\t\t\tf\"source image contains {clut.shape[0]} unique colors (must be \"\n\t\t\tf\"{numColors} or less)\"\n\t\t)\n\n\timage = image.astype(\"B\").reshape((\n\t\timageObj.height,\n\t\timageObj.width\n\t))\n\tnewObj: Image.Image = Image.fromarray(image, \"P\")\n\n\tnewObj.putpalette(clut.tobytes(), imageObj.mode)\n\treturn newObj\n\ndef getImagePalette(imageObj: Image.Image) -> NDArray[np.uint8]:\n\tclut: NDArray[np.uint8] = np.array(imageObj.getpalette(\"RGBA\"), \"B\")\n\tclut                    = clut.reshape(( -1, 4 ))\n\n\t# Pillow's PNG decoder does not handle indexed color images with alpha\n\t# correctly, so a workaround is needed here to manually integrate the\n\t# contents of the image's \"tRNs\" chunk into the palette.\n\tif \"transparency\" in imageObj.info:\n\t\talpha: bytes = imageObj.info[\"transparency\"]\n\t\tclut[:, 3]   = np.frombuffer(alpha.ljust(clut.shape[0], b\"\\xff\"))\n\n\treturn clut\n\n## Image data conversion\n\nLOWER_ALPHA_BOUND: int =  32\nUPPER_ALPHA_BOUND: int = 224\n\n# Color 0x0000 is interpreted by the PS1 GPU as fully transparent, so solid\n# black pixels must be changed to either solid dark gray or semitransparent\n# black.\nTRANSPARENT_COLOR: int = 0x0000\nBLACK_COLOR:       int = 0x0421\nSTP_BLACK_COLOR:   int = 0x8000\n\ndef to16bpp(\n\tinputData:   NDArray[np.uint8],\n\tforceSTP:    bool = False,\n\tuseSTPBlack: bool = False\n) -> NDArray[np.uint16]:\n\tsource: NDArray[np.uint16] = inputData.astype(\"<H\")\n\tr:      NDArray[np.uint16] = ((source[:, :, 0] * 31) + 127) // 255\n\tg:      NDArray[np.uint16] = ((source[:, :, 1] * 31) + 127) // 255\n\tb:      NDArray[np.uint16] = ((source[:, :, 2] * 31) + 127) // 255\n\n\tsolid:           NDArray[np.uint16] = r | (g << 5) | (b << 10)\n\tsemitransparent: NDArray[np.uint16] = solid | (1 << 15)\n\n\tdata: NDArray[np.uint16] = np.full_like(solid, TRANSPARENT_COLOR)\n\n\tif inputData.shape[2] == 4:\n\t\talpha: NDArray[np.uint8] = inputData[:, :, 3]\n\telse:\n\t\talpha: NDArray[np.uint8] = np.full(inputData.shape[:-1], 0xff, \"B\")\n\n\tnp.copyto(data, semitransparent, where = (alpha >= LOWER_ALPHA_BOUND))\n\n\tif not forceSTP:\n\t\tnp.copyto(data, solid, where = (alpha >= UPPER_ALPHA_BOUND))\n\t\tnp.copyto(\n\t\t\tdata,\n\t\t\tSTP_BLACK_COLOR if useSTPBlack else BLACK_COLOR,\n\t\t\twhere = (\n\t\t\t\t(alpha >= UPPER_ALPHA_BOUND) &\n\t\t\t\t(solid == TRANSPARENT_COLOR)\n\t\t\t)\n\t\t)\n\n\treturn data\n\ndef convertIndexedImage(\n\timageObj:    Image.Image,\n\tforceSTP:    bool = False,\n\tuseSTPBlack: bool = False\n) -> tuple[NDArray[np.uint8], NDArray[np.uint16]]:\n\tclut: NDArray[np.uint8] = getImagePalette(imageObj).reshape(( 1, -1, 4 ))\n\tclut                    = to16bpp(clut, forceSTP, useSTPBlack)\n\n\t# Pad the palette to 16 or 256 colors after converting it to 16bpp.\n\tnumColors: int = clut.shape[1]\n\tpadAmount: int = (16 if (numColors <= 16) else 256) - numColors\n\n\tif padAmount:\n\t\tclut = np.c_[\n\t\t\tclut,\n\t\t\tnp.zeros(( 1, padAmount ), \"<H\")\n\t\t]\n\n\timage: NDArray[np.uint8] = np.asarray(imageObj, \"B\")\n\n\tif image.shape[1] % 2:\n\t\timage = np.c_[\n\t\t\timage,\n\t\t\tnp.zeros(( imageObj.height, 1 ), \"B\")\n\t\t]\n\n\t# Pack two pixels into each byte for 4bpp images.\n\tif numColors <= 16:\n\t\timage = image[:, 0::2] | (image[:, 1::2] << 4)\n\n\t\tif image.shape[1] % 2:\n\t\t\timage = np.c_[\n\t\t\t\timage,\n\t\t\t\tnp.zeros(( imageObj.height, 1 ), \"B\")\n\t\t\t]\n\n\treturn image, clut\n\n## Main\n\ndef createParser() -> ArgumentParser:\n\tparser = ArgumentParser(\n\t\tdescription = \\\n\t\t\t\"Converts an image file into raw 16bpp image data, or 4bpp or 8bpp \"\n\t\t\t\"indexed color data plus a 16bpp palette.\",\n\t\tadd_help    = False\n\t)\n\n\tgroup = parser.add_argument_group(\"Tool options\")\n\tgroup.add_argument(\n\t\t\"-h\", \"--help\",\n\t\taction = \"help\",\n\t\thelp   = \"Show this help message and exit\"\n\t)\n\n\tgroup = parser.add_argument_group(\"Conversion options\")\n\tgroup.add_argument(\n\t\t\"-b\", \"--bpp\",\n\t\ttype    = int,\n\t\tchoices = ( 4, 8, 16 ),\n\t\tdefault = 16,\n\t\thelp    = \\\n\t\t\t\"Use specified color depth (4/8bpp indexed color or 16bpp RGB, \"\n\t\t\t\"default 16bpp)\",\n\t\tmetavar = \"4|8|16\"\n\t)\n\tgroup.add_argument(\n\t\t\"-s\", \"--force-stp\",\n\t\taction = \"store_true\",\n\t\thelp   = \\\n\t\t\t\"Set the semitransparency/blending flag on all pixels in the \"\n\t\t\t\"output image (useful when using additive or subtractive blending)\"\n\t)\n\tgroup.add_argument(\n\t\t\"-S\", \"--stp-black\",\n\t\taction = \"store_true\",\n\t\thelp   = \\\n\t\t\t\"Use semitransparent black instead of solid dark gray for black \"\n\t\t\t\"pixels in the image (useful when drawing the image with blending \"\n\t\t\t\"disabled)\"\n\t)\n\n\tgroup = parser.add_argument_group(\"File paths\")\n\tgroup.add_argument(\n\t\t\"input\",\n\t\ttype = Image.open,\n\t\thelp = \"Path to input image file\"\n\t)\n\tgroup.add_argument(\n\t\t\"imageOutput\",\n\t\ttype = FileType(\"wb\"),\n\t\thelp = \"Path to raw image data file to generate\"\n\t)\n\tgroup.add_argument(\n\t\t\"clutOutput\",\n\t\ttype  = FileType(\"wb\"),\n\t\tnargs = \"?\",\n\t\thelp  = \"Path to raw palette data file to generate\"\n\t)\n\n\treturn parser\n\ndef main():\n\tparser: ArgumentParser = createParser()\n\targs:   Namespace      = parser.parse_args()\n\n\tif args.bpp == 16:\n\t\timageData: NDArray[np.uint8] = np.asarray(args.input.convert(\"RGBA\"))\n\t\timageData                    = to16bpp(imageData, args.force_stp)\n\telse:\n\t\tif args.clutOutput is None:\n\t\t\tparser.error(\"path to palette data must be specified\")\n\n\t\ttry:\n\t\t\timage: Image.Image = quantizeImage(args.input, 2 ** args.bpp)\n\t\texcept RuntimeError as err:\n\t\t\tparser.error(err.args[0])\n\n\t\timageData, clutData = \\\n\t\t\tconvertIndexedImage(image, args.force_stp, args.stp_black)\n\n\t\twith args.clutOutput as file:\n\t\t\tfile.write(clutData)\n\n\twith args.imageOutput as file:\n\t\tfile.write(imageData)\n\nif __name__ == \"__main__\":\n\tmain()\n"
  },
  {
    "path": "tools/requirements.txt",
    "content": "# Create a Python virtual environment and install the dependencies listed below\n# by running the following commands:\n#\n# - Windows (using PowerShell):\n#     py -m venv env\n#     env\\Scripts\\Activate.ps1\n#     py -m pip install -r tools\\requirements.txt\n#\n# - Windows (using Cygwin/MSys2), Linux or macOS:\n#     python3 -m venv env\n#     source env/bin/activate\n#     pip3 install -r tools/requirements.txt\n\nnumpy  >= 1.19.4\nPillow >= 8.2.0\n"
  }
]